Работа с JWT в реальных приложениях почти всегда упирается в необходимость проверки подписи токена. Когда система использует асимметричную криптографию (например, RS256, ES256), проверка подписи требует публичного ключа. В современных архитектурах эти ключи публикуются в формате JWKS (JSON Web Key Set).
JWKS представляет собой JSON-документ, содержащий набор публичных
ключей. Обычно он доступен по URL (например,
https://auth.example.com/.well-known/jwks.json) и
обновляется при ротации ключей.
Однако постоянные сетевые запросы к JWKS-endpoint могут стать узким
местом. Именно здесь появляется идея локального JWKS — когда набор
ключей заранее загружается или хранится в памяти приложения. В
библиотеке jose для этого используется функция
createLocalJWKSet.
Локальный JWKS — это не динамический источник, а заранее подготовленный объект с ключами, который используется для проверки JWT без сетевых запросов.
В отличие от createRemoteJWKSet, который при каждом
неизвестном kid может обращаться к удалённому серверу,
локальная версия работает исключительно с уже загруженными ключами.
Основные сценарии использования:
JWKS представляет собой объект следующего вида:
{
"keys": [
{
"kty": "RSA",
"kid": "key-1",
"use": "sig",
"n": "...",
"e": "AQAB"
}
]
}
Каждый ключ содержит:
kty — тип ключа (RSA, EC и т.д.)kid — идентификатор ключаuse — назначение (обычно sig)n, e,
crv, x, y и т.д.)Функция createLocalJWKSet принимает JWKS-объект и
возвращает функцию, которая используется для поиска ключей при
верификации JWT.
Ключевой момент: никакого сетевого взаимодействия не происходит.
Простейший пример:
import { createLocalJWKSet, jwtVerify } from 'jose'
const jwks = {
keys: [
{
kty: 'RSA',
kid: 'example-key-1',
use: 'sig',
n: '...',
e: 'AQAB'
}
]
}
const JWKS = createLocalJWKSet(jwks)
const { payload } = await jwtVerify(token, JWKS)
При верификации JWT библиотека извлекает заголовок токена:
{
"alg": "RS256",
"kid": "example-key-1"
}
Далее выполняется:
kidalg)Если ключ не найден — возникает ошибка верификации.
kidНекоторые JWT могут не содержать kid. В этом случае
createLocalJWKSet пытается:
Это важный момент: локальный JWKS менее гибкий, чем удалённый, поскольку не может запрашивать новые ключи.
Вся проверка происходит в памяти, что даёт:
При использовании createRemoteJWKSet возможны:
Локальный JWKS полностью исключает эти проблемы.
Разработчик сам определяет набор доверенных ключей. Это снижает риск:
Если ключи меняются, приложение должно:
При большом количестве сервисов необходимо:
Если identity provider часто меняет ключи, локальный JWKS становится неудобным решением.
Локальный JWKS может содержать несколько ключей одновременно:
const jwks = {
keys: [
{
kty: 'RSA',
kid: 'key-1',
use: 'sig',
n: '...',
e: 'AQAB'
},
{
kty: 'RSA',
kid: 'key-2',
use: 'sig',
n: '...',
e: 'AQAB'
}
]
}
const JWKS = createLocalJWKSet(jwks)
В этом случае библиотека автоматически выбирает нужный ключ по
kid.
Основной сценарий использования — передача результата
createLocalJWKSet в jwtVerify.
import { jwtVerify, createLocalJWKSet } from 'jose'
const JWKS = createLocalJWKSet({
keys: [
{
kty: 'EC',
kid: 'ec-key-1',
crv: 'P-256',
x: '...',
y: '...',
use: 'sig'
}
]
})
const { payload, protectedHeader } = await jwtVerify(
token,
JWKS,
{
issuer: 'https://auth.example.com',
audience: 'api-service'
}
)
Здесь происходит полная проверка:
В случае локального JWKS кэширование происходит естественным образом:
kidЕсли токен подписан ключом, которого нет в JWKS:
JWKSNoMatchingKey
Причина почти всегда одна — устаревший набор ключей.
Ошибки вида:
n или e для RSAktyЕсли токен подписан, например, RS256, а ключ EC —
проверка завершится ошибкой.
Подход оправдан в ситуациях:
Удалённый JWKS:
Локальный JWKS:
В распределённых системах локальный JWKS часто используется в связке с CI/CD:
Такой подход особенно полезен в системах с высокой нагрузкой, где любая сетевой вызов в критическом пути недопустим.
Внутри jose локальный JWKS:
kiduseЭто делает поиск ключа детерминированным и быстрым.
Локальный JWKS усиливает контроль безопасности за счёт:
Однако безопасность полностью зависит от процесса доставки ключей в приложение.