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

Error message

unexpected "iss" claim value

jose (JavaScript) — JWTClaimValidationFailed, code ERR_JWT_CLAIM_VALIDATION_FAILED

What it means

The token verified cryptographically, but its `iss` claim is not equal to the `issuer` option you passed to `jwtVerify`. jose compares the strings exactly (an array of allowed issuers is also accepted), so any difference — a trailing slash, a different tenant path, another environment — fails the check. Pinning the issuer is what stops a token from another identity provider, or another tenant of the same provider, from being accepted.

Why it happens

How to fix it

  1. 1.

    Take the issuer string from the discovery document

    The `issuer` value in `/.well-known/openid-configuration` is, by specification, exactly what the provider writes into `iss`. Copy it byte for byte into your configuration.

    bash
    # The issuer string is defined by the provider's discovery document.
    curl -s https://login.example.com/.well-known/openid-configuration | jq -r .issuer
  2. 2.

    Pin that exact value in jwtVerify

    Keep the `issuer` option — without it jose does not check `iss` at all — and keep it in one constant shared by the JWKS URL and the verifier.

    js
    import { createRemoteJWKSet, jwtVerify } from "jose";
    
    const ISSUER = "https://login.example.com/"; // exactly as in "issuer" from discovery
    const jwks = createRemoteJWKSet(new URL("https://login.example.com/.well-known/jwks.json"));
    
    const { payload } = await jwtVerify(token, jwks, { algorithms: ["ES256"], issuer: ISSUER });
  3. 3.

    Accept several issuers with one key set each

    If your service really trusts more than one issuer, select the key set by the token's (unverified) `iss` from an explicit allow-list and then verify with that issuer pinned. Never let one issuer's keys verify another issuer's tokens.

    js
    import { createRemoteJWKSet, decodeJwt, jwtVerify } from "jose";
    
    // Each trusted issuer gets its own key set; an unknown iss is rejected before any key is used.
    const trusted = new Map([
      ["https://login.example.com/", createRemoteJWKSet(new URL("https://login.example.com/.well-known/jwks.json"))],
      ["https://partner.example.org", createRemoteJWKSet(new URL("https://partner.example.org/jwks"))],
    ]);
    
    const { iss } = decodeJwt(token);
    const keys = trusted.get(iss);
    if (!keys) throw new Error("untrusted issuer");
    await jwtVerify(token, keys, { algorithms: ["ES256", "RS256"], issuer: iss });

Related errors

Browse the full signature error database