Сообщение об ошибке
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`), а в наборе есть два или больше ключей подходящего типа на подходящей кривой. jose намеренно не перебирает кандидатов за вас внутри `jwtVerify`: выброшенная ошибка сама служит асинхронным итератором по ключам-кандидатам, так что перебор можно включить явно.
Почему это происходит
Издатель не указывает kid в заголовке токена
частоМногие небольшие или самописные издатели подписывают токены с заголовком, в котором нет ничего, кроме `{ "alg": "ES256" }`. Пока в JWKS один ключ, это работает; первая же ротация, при которой публикуется второй ключ, ломает проверку у всех проверяющих сторон.
У опубликованных ключей нет kid
частоДаже если в токенах есть `kid`, ключи из JWKS без `kid` не получится сопоставить с токеном по идентификатору. Во время ротации в таком наборе лежат и уходящий, и новый ключ, и оба одинаково правдоподобны.
У двух ключей один и тот же kid
иногдаНекоторые скрипты управления ключами получают `kid` из фиксированного имени или из алгоритма (`"es256"`), а не из самого ключа, поэтому у старого и нового ключа оказывается один и тот же идентификатор. Отпечатки 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 по очереди выдаёт каждый ключ-кандидат, и вы принимаете токен, только если один из этих ключей подтверждает подпись. Каждый кандидат обходится в одну проверку подписи, поэтому держите набор ключей небольшим.
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(); } }