错误信息
missing required "aud" claim
也可能输出为
- missing required "iss" claim
- missing required "exp" claim
- missing required "sub" claim
- missing required "iat" claim
声明名称随缺少的声明而变化(`"aud"`、`"iss"`、`"exp"`、`"sub"`、`"iat"`…)。`err.claim` 中是声明名称,`err.reason` 为 `"missing"`。
jose (JavaScript) — JWTClaimValidationFailed, code ERR_JWT_CLAIM_VALIDATION_FAILED, reason "missing"
含义
令牌中缺少一个由你的验证选项设为必需的声明。jose 不仅在你把某个声明列入 `requiredClaims` 时将其视为必需,只要你要求它检查某个声明,也会这样处理:`audience` 要求有 `aud`,`issuer` 要求有 `iss`,`subject` 要求有 `sub`,`maxTokenAge` 要求有 `iat`。引号中的声明名称会告诉你缺少的是哪一个;签名此前已经验证有效。
原因
某个验证选项隐含要求该声明
常见设置 `audience: "https://api.example.com"` 意味着“令牌必须是发给这个 API 的”——没有 `aud` 的令牌无法满足这一点,因此 jose 会报告该声明缺失,而不是悄悄放行。`issuer`、`subject` 和 `maxTokenAge` 也是如此。
颁发者从不写入该声明
常见自行实现的或内部的令牌服务常常省略 `aud` 或 `exp`。没有 `exp` 的令牌永不过期;没有 `aud` 的令牌可以被重放到任何信任同一颁发者的服务——这两个问题都值得从源头修复。
该声明列在 requiredClaims 中
偶见`requiredClaims: ["exp", "jti"]` 会让这些声明成为必需项,与它们的值无关。合理地缺少其中某个声明的令牌类型(例如没有 `exp` 的长期服务令牌),按设计就会在这里失败。
送到验证方的是另一种令牌
偶见刷新令牌、登出令牌或提供商特有的令牌也是 JWT,但携带的声明集合不同。如果声明集合看起来很陌生,说明发送的令牌不对。
修复方法
- 1.
记录缺失的声明和已有的声明
错误对象提供了 `claim`、`reason` 和解码后的 `payload`。记录这些信息,只需一次请求就能区分“颁发者没有写入该声明”和“发送了错误的令牌”。
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.
让颁发者写入该声明
优先修正令牌,而不是放宽验证方的要求。正是 `aud`、`iss` 和 `exp` 让 JWT 可以被安全地接受;RFC 8725 建议每个令牌都携带它们。
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);