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
A verification option implies the claim
commonSetting `audience: "https://api.example.com"` means "the token must be for this API" — a token with no `aud` cannot satisfy that, so jose reports the claim as missing rather than silently passing. The same holds for `issuer`, `subject` and `maxTokenAge`.
The issuer never writes that claim
commonHand-rolled or internal token services often omit `aud` or `exp`. Tokens without `exp` never expire; tokens without `aud` can be replayed to any service that trusts the same issuer — both are worth fixing at the source.
The claim is listed in requiredClaims
occasional`requiredClaims: ["exp", "jti"]` makes those claims mandatory regardless of their values. A token type that legitimately lacks one of them (for example a long-lived service token without `exp`) fails here by design.
A different kind of token reached the verifier
occasionalRefresh tokens, logout tokens or provider-specific tokens are also JWTs but carry different claim sets. If the claim set looks unfamiliar, the wrong token was sent.
How to fix it
- 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.
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);