JWKSNoMatchingKey и JWKSMultipleMatchingKeys

При верификации JWT через jose и удалённый JWKS (JSON Web Key Set) библиотека выполняет сопоставление ключа подписи токена с набором ключей, полученных из JWKS endpoint. Ошибки JWKSNoMatchingKey и JWKSMultipleMatchingKeys возникают на этапе выбора подходящего ключа и почти всегда связаны с некорректной конфигурацией ключей или несогласованностью между issuer и JWKS.


При вызове:

import { jwtVerify, createRemoteJWKSet } from 'jose'

const JWKS = createRemoteJWKSet(new URL('https://example.com/.well-known/jwks.json'))

const { payload } = await jwtVerify(token, JWKS)

библиотека выполняет последовательность шагов:

  • извлекает kid (Key ID) из заголовка JWT
  • загружает JWKS (если кэш устарел)
  • фильтрует ключи по kid
  • дополнительно проверяет совместимость по алгоритму (alg, kty, use)
  • выбирает единственный подходящий ключ

Любое отклонение от однозначного соответствия приводит к ошибкам выбора ключа.


JWKSNoMatchingKey

Ошибка JWKSNoMatchingKey возникает, когда среди всех ключей JWKS не найден ни один подходящий ключ для проверки подписи JWT.

Основные причины

1. Несоответствие kid

JWT содержит:

{
  "kid": "abc123"
}

но в JWKS нет ключа с таким kid.

JWKS:

{
  "keys": [
    { "kid": "def456", "kty": "RSA", "use": "sig" }
  ]
}

2. Ротация ключей без синхронизации

Старый ключ удалён из JWKS, но токены ещё подписываются им.


3. Неправильный issuer или JWKS URL

Частая ошибка при работе с несколькими окружениями:

  • токен выпущен одним auth-сервером
  • JWKS загружается с другого домена

4. Несовместимость алгоритмов

Например:

  • JWT подписан RS256
  • JWKS содержит только EC ключи

Отладка JWKSNoMatchingKey

Полезно проверять:

console.log(protectedHeader)
console.log(await JWKS(protectedHeader, undefined))

Также важно убедиться, что:

  • kid присутствует в JWKS
  • JWKS endpoint возвращает актуальные ключи
  • алгоритмы совпадают (alg, kty)

JWKSMultipleMatchingKeys

Ошибка JWKSMultipleMatchingKeys возникает, когда найдено несколько ключей, подходящих для проверки одного JWT. Это более редкая, но критическая ситуация, указывающая на неоднозначность в JWKS.

Типичные причины

1. Дублирование kid

JWKS содержит несколько ключей с одинаковым kid:

{
  "keys": [
    { "kid": "abc123", "kty": "RSA" },
    { "kid": "abc123", "kty": "RSA" }
  ]
}

2. Перекрывающиеся ключи при ротации

При плавной ротации ключей старый и новый ключ могут временно иметь одинаковые параметры:

  • одинаковый kid
  • одинаковый alg
  • одинаковый use: sig

3. Некорректная агрегация JWKS

При объединении нескольких источников JWKS (например, multi-tenant auth):

  • ключи разных провайдеров могут пересекаться по kid

4. Ошибки прокси или кеширования

Иногда CDN или кеширующий слой возвращает дубликаты ключей.


Поведение jose при конфликте

При обнаружении нескольких подходящих ключей библиотека не выбирает “первый попавшийся”, а явно выбрасывает:

JWKSMultipleMatchingKeys: multiple matching keys found in JWKS

Это сделано для предотвращения:

  • недетерминированной проверки подписи
  • атак с подменой ключа при неоднозначности

Разбор процесса выбора ключа

Логика внутри jose при поиске ключа:

  1. Фильтрация по kid
  2. Фильтрация по alg
  3. Фильтрация по use: sig
  4. Проверка допустимости криптографического типа (kty)
  5. Проверка пригодности для verify

Результат должен быть строго один ключ.


Практические сценарии возникновения ошибок

Сценарий 1: неправильный rotation strategy

При обновлении ключей:

  • новый ключ добавлен
  • старый не удалён
  • оба имеют одинаковый kid

Результат: JWKSMultipleMatchingKeys


Сценарий 2: multi-region JWKS merge

Два региона публикуют JWKS:

  • region A: kid = key-1
  • region B: kid = key-1

После агрегации возникает конфликт.


Сценарий 3: mismatch между auth server и verifier

JWT выдан сервером A, JWKS берётся с сервера B:

  • ключи различаются полностью
  • JWKSNoMatchingKey

Способы устранения JWKSNoMatchingKey

  • проверка совпадения iss и JWKS endpoint
  • синхронизация ротации ключей
  • сохранение старых ключей до истечения всех токенов
  • логирование kid всех входящих JWT

Способы устранения JWKSMultipleMatchingKeys

  • обеспечить уникальность kid во всей системе
  • запретить дублирование ключей при деплое
  • централизовать генерацию JWKS
  • валидировать JWKS перед публикацией

Проверка JWKS перед отдачей:

const kids = jwks.keys.map(k => k.kid)
const hasDuplicates = new Set(kids).size !== kids.length

Особенности работы createRemoteJWKSet

createRemoteJWKSet кэширует JWKS и может временно удерживать устаревшие ключи. Это влияет на:

  • задержки при ротации
  • кратковременные конфликты ключей

Настройки кэширования могут быть критичны в системах с частой сменой ключей.


Практика безопасной конфигурации

  • каждый ключ должен иметь уникальный kid
  • старые ключи не удаляются мгновенно
  • JWKS endpoint должен возвращать только актуальный набор без дублей
  • все окружения должны использовать один источник ключей
  • алгоритмы подписания должны быть строго ограничены (например, только RS256)