Skip to content
ecdsa.com
jose (JavaScript)JWT libraries

Error message

unexpected "aud" claim value

jose (JavaScript) — JWTClaimValidationFailed, code ERR_JWT_CLAIM_VALIDATION_FAILED

What it means

The signature verified, but the token's `aud` claim does not contain the value you passed as `audience` to `jwtVerify`. jose compares exact strings: the check passes when at least one value in the token's `aud` (a string or an array) equals one of the audiences you allow. The token is authentic — it was simply issued for a different recipient, so accepting it would let a token minted for one API be replayed against another.

Why it happens

How to fix it

  1. 1.

    Print what the token carries next to what you expect

    Decode the claims without trusting them and compare `aud` with your `audience` option character by character. Fix whichever side is wrong — the verifier's option or the issuer's configuration — but keep the check.

    js
    import { decodeJwt, jwtVerify } from "jose";
    
    console.log("token aud:", decodeJwt(token).aud); // string or array of strings
    
    await jwtVerify(token, jwks, {
      algorithms: ["ES256"],
      issuer: "https://issuer.example.com",
      audience: "https://api.example.com/orders", // exact string match
    });
  2. 2.

    Request a token for the API you are calling

    When the token was issued for another audience, the fix belongs on the client: name the target API in the token request so the authorization server puts it into `aud`.

    bash
    # OAuth 2.0 client credentials: ask the authorization server for a token
    # minted for the API you are about to call (Auth0 uses "audience",
    # RFC 8707 resource indicators use "resource").
    curl -s https://issuer.example.com/oauth/token \
      -d grant_type=client_credentials \
      -d client_id="$CLIENT_ID" -d client_secret="$CLIENT_SECRET" \
      -d audience=https://api.example.com/orders
  3. 3.

    Allow several identifiers explicitly, if they are all yours

    `audience` accepts an array. List every identifier that legitimately names this API (for example a migration from a URL to a logical name), rather than removing the option — without `audience`, jose does not check `aud` at all.

    js
    await jwtVerify(token, jwks, {
      algorithms: ["ES256"],
      audience: ["https://api.example.com/orders", "orders-api"], // any one must match
    });

Related errors

Browse the full signature error database