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

Error message

missing required "aud" claim

Also printed as

  • missing required "iss" claim
  • missing required "exp" claim
  • missing required "sub" claim
  • missing required "iat" claim

The claim name changes with the missing claim (`"aud"`, `"iss"`, `"exp"`, `"sub"`, `"iat"`…). `err.claim` holds the name and `err.reason` is `"missing"`.

jose (JavaScript) — JWTClaimValidationFailed, code ERR_JWT_CLAIM_VALIDATION_FAILED, reason "missing"

What it means

The token does not contain a claim that your verification options make mandatory. jose treats a claim as required not only when you list it in `requiredClaims`, but also whenever you ask it to check that claim: `audience` requires `aud`, `issuer` requires `iss`, `subject` requires `sub` and `maxTokenAge` requires `iat`. The claim name in quotes tells you which one is absent; the signature was already valid.

Why it happens

How to fix it

  1. 1.

    Log the missing claim and the claims that are present

    The error object exposes `claim`, `reason` and the decoded `payload`. Logging them distinguishes "issuer does not write it" from "the wrong token was sent" in one request.

    js
    import { errors, jwtVerify } from "jose";
    
    try {
      await jwtVerify(token, jwks, { algorithms: ["ES256"], audience: "https://api.example.com" });
    } catch (err) {
      if (err instanceof errors.JWTClaimValidationFailed && err.reason === "missing") {
        console.error("token lacks claim:", err.claim, "claims present:", Object.keys(err.payload));
      }
      throw err;
    }
  2. 2.

    Have the issuer include the claim

    Prefer fixing the token over relaxing the verifier. `aud`, `iss` and `exp` are what make a JWT safe to accept; RFC 8725 recommends that every token carries them.

    js
    import { SignJWT } from "jose";
    
    const token = await new SignJWT({ scope: "orders:read" })
      .setProtectedHeader({ alg: "ES256", kid })
      .setIssuer("https://issuer.example.com")
      .setAudience("https://api.example.com")   // the claim the verifier requires
      .setIssuedAt()
      .setExpirationTime("10m")
      .sign(privateKey);

Related errors

Browse the full signature error database