Error message
multiple matching keys found in the JSON Web Key Set
jose (JavaScript) — JWKSMultipleMatchingKeys, code ERR_JWKS_MULTIPLE_MATCHING_KEYS
What it means
More than one key in the JSON Web Key Set could verify this token, and jose refuses to guess. This happens when the token header has no `kid` (or several keys share the same `kid`) and the set contains two or more keys of the right type and curve. jose deliberately does not try the candidates for you inside `jwtVerify`; the thrown error is itself an async iterator over the candidate keys, so you can opt in to trying them.
Why it happens
The issuer does not put kid in the token header
commonMany small or hand-written issuers sign with `{ "alg": "ES256" }` only. As long as the JWKS holds a single key this works; the first rotation that publishes a second key breaks every verifier.
The published keys have no kid
commonEven if tokens carry a `kid`, a JWKS whose keys lack `kid` cannot be matched by identifier. During rotation such a set contains the outgoing and the incoming key, both equally plausible.
Two keys share one kid
occasionalSome key-management scripts derive `kid` from a fixed name or from the algorithm (`"es256"`) instead of the key itself, so the old and new key end up with the same identifier. RFC 7638 thumbprints avoid this.
How to fix it
- 1.
Identify the signing key in every token
The robust fix is on the issuer: give each key a unique `kid` (an RFC 7638 thumbprint works well), publish it in the JWKS, and write it into the protected header of every token.
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.
Opt in to trying the candidates
When you cannot change the issuer, iterate over the error: jose yields each candidate key, and you accept the token only if one of them verifies. This costs one signature check per candidate, so keep the key set small.
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(); } }