Короткоживущие access-токены используются как основной механизм ограничения времени жизни сессии пользователя при работе с API. Их ключевая характеристика — минимальный срок действия (обычно от 5 до 30 минут), что снижает риск компрометации при утечке токена.
Access-токен содержит минимальный набор данных: идентификатор пользователя, набор прав (scopes), время выдачи и срок истечения. В большинстве современных систем он реализуется как JWT (JSON Web Token), подписанный сервером авторизации.
Использование короткого времени жизни решает несколько задач:
JWT представляет собой компактный токен, состоящий из трёх частей: header, payload и signature. Подпись обеспечивает целостность данных и проверку подлинности.
В JavaScript-экосистеме одной из основных библиотек для работы с JWT является jose. Она реализует современные стандарты JWS, JWE и JWT с поддержкой Web Crypto API и строгой типизацией операций криптографии.
Ключевая особенность библиотеки — отказ от небезопасных абстракций и предоставление низкоуровневого контроля над криптографическими операциями.
Пример создания access-токена:
import { SignJWT } from 'jose'
const secret = new TextEncoder().encode('super-secret-key')
const accessToken = await new SignJWT({ role: 'user' })
.setProtectedHeader({ alg: 'HS256' })
.setSubject('user-123')
.setIssuedAt()
.setExpirationTime('15m')
.sign(secret)
Здесь фиксируется ключевой принцип: время жизни токена задаётся явно и коротко.
Долгоживущие access-токены создают критическую уязвимость: при компрометации злоумышленник получает доступ на длительный период без возможности быстрого отзыва.
Решение — разделение токенов на два уровня:
Однако наличие refresh-токена вводит новую проблему: необходимость его защиты и регулярной ротации.
Ротация refresh-токенов заключается в том, что при каждом обновлении access-токена выдается новый refresh-токен, а старый немедленно инвалидируется.
Это позволяет:
Типичный поток выглядит следующим образом:
Jose используется в основном для создания и проверки JWT, включая refresh-токены.
import { SignJWT } from 'jose'
const refreshSecret = new TextEncoder().encode('refresh-secret')
const refreshToken = await new SignJWT({ type: 'refresh' })
.setProtectedHeader({ alg: 'HS256' })
.setSubject('user-123')
.setIssuedAt()
.setExpirationTime('7d')
.sign(refreshSecret)
Refresh-токен имеет значительно больший срок жизни, но его использование строго ограничено серверной логикой.
import { jwtVerify } from 'jose'
const { payload } = await jwtVerify(
token,
refreshSecret
)
if (payload.type !== 'refresh') {
throw new Error('Invalid token type')
}
На уровне архитектуры важно связывать оба токена с одной сессией. Обычно для этого используется:
Пример payload refresh-токена:
{
"sub": "user-123",
"type": "refresh",
"sid": "session-456"
}
При ротации критично предотвращать replay-атаки. Для этого применяется хранение состояния refresh-токенов:
Пример логики:
const stored = await db.refreshTokens.find(tokenId)
if (!stored || stored.used) {
throw new Error('Token reuse detected')
}
await db.refreshTokens.update(tokenId, { used: true })
Access-токены обычно не хранятся на сервере, что делает систему stateless. Однако это создаёт ограничение: невозможность мгновенной инвалидизации.
Компенсация достигается комбинацией:
Jose поддерживает два ключевых стандарта:
Для access-токенов обычно достаточно JWS. JWE применяется в случаях, когда payload содержит чувствительные данные.
Пример JWE:
import { EncryptJWT } from 'jose'
const encrypted = await new EncryptJWT({ role: 'admin' })
.setProtectedHeader({ alg: 'dir', enc: 'A256GCM' })
.setIssuedAt()
.setExpirationTime('10m')
.encrypt(secretKey)
Архитектурно разделение выглядит следующим образом:
access token:
refresh token:
Такое разделение снижает риск компрометации системы даже при утечке одного из компонентов.
На практике часто встречаются критические ошибки:
Каждая из этих ошибок снижает эффективность всей модели безопасности.
Использование короткоживущих access-токенов изменяет модель угроз. Даже при успешной атаке:
Это делает систему устойчивой к перехвату токенов в сетевом трафике или XSS-атакам с ограниченным временем воздействия.