JWT (JSON Web Token) — компактный способ передачи утверждений (claims) между сторонами. Токен состоит из трёх частей в кодировке Base64URL, разделённых точками. В этой статье рассмотрены структура JWT, алгоритмы подписи и рекомендации по безопасности.
Структура JWT
Токен имеет вид: header.payload.signature
Header
Содержит тип токена и алгоритм подписи:
{
"alg": "HS256",
"typ": "JWT"
}
Payload
Утверждения (claims) — данные, которые передаются в токене. Стандартные поля:
| Поле | Описание |
|---|---|
sub | Subject — идентификатор субъекта (например, user id) |
iat | Issued At — время создания |
exp | Expiration — время истечения |
iss | Issuer — издатель токена |
aud | Audience — получатель |
Пример:
{
"sub": "user-123",
"iat": 1709827200,
"exp": 1709913600,
"role": "admin"
}
Signature
Подпись формируется так:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
Сервер проверяет подпись, чтобы убедиться, что токен не изменён и выдан доверенным издателем.
Алгоритмы подписи
| Алгоритм | Тип | Описание |
|---|---|---|
| HS256 | Симметричный | Один секрет у клиента и сервера |
| RS256 | Асимметричный | Приватный ключ для подписи, публичный для проверки |
| ES256 | Асимметричный | ECDSA с P-256 |
Для публичных API предпочтительнее RS256: сервер хранит приватный ключ, клиенты проверяют подпись публичным ключом.
Безопасность
Не храните чувствительные данные в payload
Payload кодируется Base64, но не шифруется. Любой может декодировать и прочитать содержимое. Не помещайте пароли, ключи, персональные данные.
Используйте короткое время жизни
exp должен быть разумным (минуты для access token, часы или дни для refresh — в зависимости от стратегии).
Защита от перехвата
Передавайте JWT по HTTPS. Для веб-приложений предпочтительнее httpOnly cookie, а не localStorage, чтобы снизить риск XSS.
Алгоритм none
Отключите поддержку alg: none. Атаки с подменой алгоритма возможны при неправильной реализации проверки.
Декодирование и отладка
JWT Decoder на rechecker.ru позволяет вставить токен и просмотреть декодированные header и payload. Удобно для отладки. Не используйте с реальными production-токенами в публичных средах.
Проверка на сервере
Псевдокод проверки:
- Разбить токен по
. - Декодировать header и payload
- Проверить
exp(токен не истёк) - Проверить
aud,issпри необходимости - Вычислить подпись и сравнить с переданной
Используйте проверенные библиотеки (например, jsonwebtoken в Node.js), а не самописную реализацию.
Refresh токены
Access token — короткоживущий (15–60 минут). Refresh token — долгоживущий, используется для получения новой пары. Храните refresh token безопасно (httpOnly cookie, защищённое хранилище). При компрометации access token ущерб ограничен временем жизни.
Blacklist и отзыв
JWT по умолчанию нельзя отозвать до истечения. Для немедленного отзыва используют blacklist (список отозванных токенов) или короткое время жизни с refresh. Blacklist требует хранилища (Redis, БД) и проверки при каждом запросе.
Дополнительные материалы
Реализация аутентификации с JWT на практике — в Аутентификация через JWT: пошаговая реализация.
Структура payload
Рекомендуемые поля для access token:
sub— идентификатор пользователя (обязательно)exp— время истечения (обязательно)iat— время выдачи (рекомендуется)iss— издатель (для мультитенантных систем)aud— аудитория (если токен для конкретного сервиса)
Кастомные claims (роль, права) добавляйте по необходимости. Не перегружайте payload — токен передаётся с каждым запросом.
Base64URL
JWT использует Base64URL: алфавит Base64 без +, /, с заменой = на пустую строку. Строки безопасны для URL и заголовков. Не путайте с обычным Base64.
Проверка алгоритма
При верификации всегда проверяйте, что алгоритм в header совпадает с ожидаемым. Атака «alg: none» или подмена HS256 на RS256 возможна при неправильной реализации. Библиотеки вроде jsonwebtoken делают это по умолчанию.
JWT в URL
Передача токена в query-параметре не рекомендуется: URL попадает в логи, историю. Используйте заголовок Authorization: Bearer <token> или cookie.
Сроки хранения
Типичные значения: access token 15–60 минут, refresh token 7–30 дней. Для высокопривилегированных операций — короче. Баланс между безопасностью и удобством (частота повторного логина).
Инструменты
JWT Decoder на rechecker.ru — декодирование header и payload без проверки подписи. Удобно для отладки. Не загружайте production-токены в публичные инструменты.
JWT vs сессии
| Критерий | JWT | Сессии |
|---|---|---|
| Хранение состояния | Stateless (данные в токене) | Stateful (сервер хранит сессию) |
| Масштабирование | Проще (нет общего хранилища) | Требует shared storage (Redis) |
| Отзыв | Сложнее (до истечения) | Проще (удалить сессию) |
| Размер | Передаётся с каждым запросом | Только session ID |
Выбор зависит от архитектуры. Для микросервисов и распределённых систем JWT часто удобнее. Для монолита с критичной возможностью немедленного отзыва — сессии.
Атаки на JWT
- Подмена алгоритма — использование
alg: noneили смена HS256 на RS256 с подменой ключа. Защита: проверка алгоритма на сервере. - Кража токена — XSS, перехват трафика. Защита: HTTPS, httpOnly cookie, Content Security Policy.
- Слабый секрет — при HS256 секрет должен быть достаточно длинным и случайным. Защита: минимум 256 бит для HS256, предпочтительнее RS256.
Библиотеки
Популярные библиотеки для работы с JWT:
- Node.js: jsonwebtoken
- Python: PyJWT
- Java: jjwt
- PHP: firebase/php-jwt
Все они поддерживают верификацию подписи, проверку exp/iat и кастомные claims.
JWE (JSON Web Encryption)
JWT по умолчанию не шифрует payload — только подписывает. Для конфиденциальных данных используйте JWE (JSON Web Encryption), который шифрует содержимое. JWE имеет другую структуру и требует поддержки в библиотеке.
Конфиденциальность
Не помещайте в JWT персональные данные (email, телефон, адрес), если токен может попасть в логи или к третьим сторонам. Минимизируйте payload — только идентификатор и необходимые claims.
Время в claims
iat и exp — Unix timestamp (секунды). Учитывайте разницу часовых поясов между серверами. Рекомендуется использовать UTC везде.
Длина токена
JWT передаётся с каждым запросом. Большой payload увеличивает трафик. Для API с частыми запросами держите payload минимальным: sub, exp, при необходимости role или scope.