エラーメッセージ
unexpected "aud" claim value
jose (JavaScript) — JWTClaimValidationFailed, code ERR_JWT_CLAIM_VALIDATION_FAILED
このエラーの意味
署名の検証には成功しましたが、トークンの `aud` クレームに、`jwtVerify` の `audience` に渡した値が含まれていません。jose は文字列を完全一致で比較します。トークンの `aud`(文字列または配列)の値の少なくとも 1 つが、許可したオーディエンスのいずれかと等しければ、確認は成功します。トークン自体は本物で、単に別の受信者向けに発行されたものです。これを受け入れると、ある API 向けに発行されたトークンを別の API でリプレイできてしまいます。
発生する理由
トークンが別の API 向けに発行されている
よくある1 つの認可サーバーが複数のリソースサーバーを担当していることはよくあります。注文 API 用に取得したトークンを請求 API に送ったクライアントには、まさにこのエラーが返ります。確認が正しく機能しているということです。エラーの `err.payload` プロパティで、トークンに実際に含まれているクレームを確認できます。
同じ API だが識別子の表記が異なる
よくあるオーディエンスの照合はバイト単位の完全一致です。`https://api.example.com` と `https://api.example.com/`(末尾のスラッシュ)、http と https、`api://orders` のような論理名と API の URL は、いずれも異なる値です。ID プロバイダーは設定にある識別子をそのままコピーするため、検証者も同じ表記を使わなければなりません。
アクセストークンが必要な箇所に ID トークンを送っている
ときどきOpenID Connect では、ID トークンの `aud` は API ではなく、ユーザーをログインさせたアプリケーションのクライアント ID です。そのため、アクセストークンではなく ID トークンを転送するフロントエンドは、その背後にあるすべての API でオーディエンスの確認に失敗します。
クライアントが特定のオーディエンスを要求していない
ときどき一部のプロバイダー(Auth0、Okta のカスタム認可サーバー、オーディエンスマッパーを設定した Keycloak)は、トークンリクエストで API が指定された場合にのみ、`aud` にその API を入れます。`audience` パラメーターや RFC 8707 の `resource` パラメーターがなければ、トークンは userinfo エンドポイントなどのデフォルトのオーディエンス向けに発行されます。
修正方法
- 1.
トークンの内容と想定する値を並べて出力する
クレームを信頼しないままデコードし、`aud` と `audience` オプションを 1 文字ずつ比較してください。誤っている側(検証者のオプションか、発行者の設定か)を修正し、確認自体は残してください。
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.
呼び出す API 向けのトークンを要求する
トークンが別のオーディエンス向けに発行されている場合、修正すべきはクライアント側です。トークンリクエストで対象の API を指定し、認可サーバーがそれを `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.
すべて自分の識別子であれば、複数を明示的に許可する
`audience` には配列を指定できます。オプションを削除するのではなく、この API を正当に指す識別子をすべて列挙してください(たとえば URL から論理名への移行中など)。`audience` がなければ、jose は `aud` をまったく確認しません。
js await jwtVerify(token, jwks, { algorithms: ["ES256"], audience: ["https://api.example.com/orders", "orders-api"], // any one must match });