Ротация ключей в системах, использующих JOSE (JSON Object Signing and Encryption), представляет собой процесс регулярной замены криптографических ключей, применяемых для подписи и проверки JWT (JSON Web Token). Основная цель этой практики — снижение риска компрометации ключей и ограничение времени их потенциального злоупотребления.
В экосистеме JavaScript библиотека jose реализует полный стек JOSE-операций: подпись, проверку, шифрование и работу с JWK/JWKS. При построении системы аутентификации на основе JWT ключевая проблема заключается не в самой подписи токенов, а в управлении жизненным циклом ключей.
В основе лежит разделение ролей ключей:
Каждый ключ в JWKS имеет идентификатор kid (Key ID),
который позволяет однозначно сопоставить токен и ключ проверки.
{
"keys": [
{
"kty": "RSA",
"kid": "key-2026-01",
"use": "sig",
"alg": "RS256",
"n": "...",
"e": "AQAB"
}
]
}
Именно kid становится фундаментом ротации: система
должна уметь одновременно работать с несколькими активными ключами.
Ротация ключей решает несколько критических задач:
При отсутствии ротации ключ превращается в постоянную точку отказа всей системы аутентификации.
Наиболее распространённая модель — overlapping rotation (перекрывающаяся ротация).
Её принцип:
Ключевой момент — система проверки должна поддерживать несколько ключей одновременно.
Подпись JWT с указанием kid:
import { SignJWT } from 'jose'
const secret = new TextEncoder().encode('old-or-current-secret')
const token = await new SignJWT({ sub: '123' })
.setProtectedHeader({ alg: 'HS256', kid: 'key-2026-01' })
.setIssuedAt()
.setExpirationTime('2h')
.sign(secret)
На практике для продакшн-систем чаще используется RSA или ECDSA:
import { generateKeyPair, SignJWT, exportJWK } from 'jose'
const { privateKey, publicKey } = await generateKeyPair('RS256')
const jwk = await exportJWK(publicKey)
jwk.kid = 'key-2026-02'
jwk.use = 'sig'
jwk.alg = 'RS256'
При наличии нескольких ключей JWKS выглядит как набор:
{
"keys": [
{
"kid": "key-2025-12",
"kty": "RSA",
"use": "sig",
"alg": "RS256"
},
{
"kid": "key-2026-01",
"kty": "RSA",
"use": "sig",
"alg": "RS256"
}
]
}
Система должна гарантировать:
Один из наиболее предсказуемых подходов — ротация по расписанию.
Характерные интервалы:
Пример логики:
function getCurrentKey() {
const now = new Date()
if (now.getUTCDate() % 2 === 0) {
return 'key-even'
}
return 'key-odd'
}
В реальных системах вместо условных проверок используется хранилище конфигурации или KMS.
Более контролируемая стратегия — versioned keys.
Каждый ключ получает:
Пример структуры:
{
"kid": "rsa-v3-2026",
"version": 3,
"status": "active"
}
При генерации токена всегда выбирается ключ с максимальной версией и
статусом active.
При верификации JWT библиотека jose использует kid для
выбора ключа:
import { jwtVerify } from 'jose'
async function verify(token, jwks) {
return jwtVerify(token, async (header) => {
const key = jwks.keys.find(k => k.kid === header.kid)
if (!key) {
throw new Error('Unknown key')
}
return key
})
}
Такой подход позволяет:
В продакшн-системах ключи обычно публикуются через endpoint:
https://auth.example.com/.well-known/jwks.json
Клиенты:
Проблема ротации здесь — синхронизация кешей. Если ключ удалён слишком рано, старые токены начинают падать.
Поэтому вводится правило:
ключ должен оставаться доступным дольше максимального времени жизни токена
Практика безопасной ротации включает grace period:
Типичная формула:
grace_period ≥ max(token_lifetime)
Если токены живут 2 часа, ключи часто держат 24–72 часа.
В современных архитектурах ключи часто хранятся в KMS (Key Management System):
В этом случае jose используется только как криптографический слой, а управление ключами делегируется инфраструктуре.
Пример абстракции:
async function signJWT(payload) {
const key = await kms.getActiveSigningKey()
return new SignJWT(payload)
.setProtectedHeader({ alg: 'RS256', kid: key.kid })
.sign(key.privateKey)
}
Типичные проблемы:
Приводит к невозможности проверки активных токенов.
kidБез идентификатора система вынуждена пробовать все ключи.
Клиенты продолжают использовать устаревший набор ключей.
Нарушает детерминированность системы.
Чтобы ротация не влияла на работу системы, применяется последовательность:
Важное свойство — обратная совместимость всегда сохраняется.
В процессе ротации может происходить не только смена ключа, но и алгоритма:
JWKS в этом случае содержит ключи с разными alg, а
система должна учитывать это при верификации.
const key = jwks.keys.find(k =>
k.kid === header.kid && k.alg === header.alg
)
Типичная схема включает:
Поток:
Библиотека jose обеспечивает все необходимые примитивы:
Ротация реализуется не внутри библиотеки, а на уровне архитектуры, где jose выступает криптографическим движком, а не системой управления ключами.
Основная сложность всегда находится не в криптографии, а в координации жизненного цикла ключей между сервисами и клиентами.