エラーメッセージ
multiple matching keys found in the JSON Web Key Set
jose (JavaScript) — JWKSMultipleMatchingKeys, code ERR_JWKS_MULTIPLE_MATCHING_KEYS
このエラーの意味
鍵セット(JSON Web Key Set)の中に、このトークンを検証できる鍵が複数あり、jose は推測で選ぶことを拒否しました。これは、トークンのヘッダーに `kid` がなく(または複数の鍵が同じ `kid` を共有しており)、鍵セットに種類と曲線が合う鍵が 2 つ以上含まれている場合に起こります。jose は意図的に、`jwtVerify` の内部で候補の鍵を代わりに試すことはしません。スローされるエラー自体が候補の鍵を順に返す非同期イテレーターになっているため、明示的にオプトインすれば候補を試せます。
発生する理由
発行者がトークンのヘッダーに kid を入れていない
よくある小規模な発行者や自作の発行者の多くは、`{ "alg": "ES256" }` だけで署名しています。JWKS に鍵が 1 つしかないうちはこれで動作しますが、2 つ目の鍵を公開する最初のローテーションで、すべての検証者が動かなくなります。
公開されている鍵に kid がない
よくあるトークンに `kid` が含まれていても、鍵に `kid` がない JWKS では識別子による照合ができません。ローテーション中、そのような鍵セットには退役する鍵と新たに導入される鍵の両方が含まれ、どちらも同じくらい有力な候補になります。
2 つの鍵が同じ kid を共有している
ときどき鍵管理スクリプトの中には、鍵そのものではなく、固定の名前やアルゴリズム(`"es256"`)から `kid` を導出するものがあり、古い鍵と新しい鍵が同じ識別子になってしまいます。RFC 7638 のサムプリントを使えば、これを防げます。
修正方法
- 1.
すべてのトークンで署名鍵を識別できるようにする
確実な修正は発行者側で行います。各鍵に一意の `kid` を割り当て(RFC 7638 のサムプリントが適しています)、それを JWKS で公開し、すべてのトークンの保護ヘッダーに書き込んでください。
js import { SignJWT } from "jose"; // Issuer side: name the key in every token, and publish the same kid in the JWKS. const token = await new SignJWT(claims) .setProtectedHeader({ alg: "ES256", kid: "2026-10" }) .setExpirationTime("10m") .sign(privateKey); - 2.
候補の鍵を順に試す処理を明示的に有効にする
発行者を変更できない場合は、エラーを反復処理してください。jose は候補の鍵を 1 つずつ返すので、そのいずれかで検証に成功した場合にのみトークンを受け入れます。候補ごとに署名の検証が 1 回かかるため、鍵セットは小さく保ってください。
js import { errors, jwtVerify } from "jose"; async function verifyWithCandidates(token, jwks, options) { try { return await jwtVerify(token, jwks, options); } catch (err) { if (err?.code !== "ERR_JWKS_MULTIPLE_MATCHING_KEYS") throw err; for await (const publicKey of err) { try { return await jwtVerify(token, publicKey, options); } catch (inner) { if (inner?.code === "ERR_JWS_SIGNATURE_VERIFICATION_FAILED") continue; throw inner; } } throw new errors.JWSSignatureVerificationFailed(); } }