JWT для сессий (антипаттерн)
- title
- JWT для сессий (антипаттерн)
- type
- concept
- summary
- Почему JWT для сессионных токенов - ошибка: несовпадение сроков жизни, мнимая stateless-природа, грабли в спецификации и альтернативы
- tags
- authentication, security, web
- sources
- samsch-jwt-auth-gist
- created
- 2026-04-25
- updated
- 2026-04-25
- lang
- ru
- translation_of
- jwt-for-sessions
- source_updated
- 2026-04-25
- translated
- 2026-09-01
- translator
- lllm/antigravity/gemini-3.7-flash-medium
Популярный в программах JavaScript-буткемпов и обучающих руководствах паттерн: использовать JSON Web Tokens (JWT, RFC 7519) в качестве учётных данных пользовательской сессии. После входа сервер возвращает JWT с утверждениями (claims) о личности пользователя; клиент сохраняет его (часто в localStorage) и отправляет с каждым запросом, обычно как Authorization: Bearer <token>. Сервер проверяет подпись JWT при каждом запросе, никуда дополнительно не обращаясь. Это продают под видом "stateless-аутентификации".
В ряде известных статей (joepie91 2016, Paragon Initiative 2017, gist от samsch) авторы сходятся во мнении, что это неподходящий инструмент. Стандартная альтернатива - обычные сессии на cookie.
Почему это не подходит
Несовпадение срока жизни. JWT спроектированы для короткоживущих токенов (около 5 минут или меньше). Сессиям требуется гораздо более долгий срок жизни - часы, дни, иногда недели. Растягивание этого срока превращает каждый недостаток архитектуры в уязвимость, которая только усугубляется со временем.
Отзыв токенов трудно прикрутить задним числом. Проверка исключительно по подписи не позволяет отозвать токен до наступления exp. В реальных системах нужно выходить из аккаунта, сбрасывать скомпрометированные учётные записи или на лету менять права администратора. Попытка прикрутить отзыв к JWT требует серверного чёрного списка, который приходится проверять при каждом запросе - в этот момент свойство "stateless" пропадает, и JWT больше не даёт ничего, с чем ID сессии не справился бы лучше.
Stateless-подход - фикция при любом разумном уровне безопасности. Настоящая аутентификация требует отзыва, ротации ключей, защиты от повторного воспроизведения (replay attacks) и сброса сессий при смене пароля. Всему этому нужно состояние на сервере. Аргумент samsch (и joepie91) заключается в следующем: если хранилище данных всё равно необходимо, храните в нём всё и просто выдавайте клиенту непрозрачный ID сессии. JWT даёт лишь передачу полезной нагрузки по сети, что только раздувает объём данных и не приносит никакой пользы.
Семейство спецификаций JWT/JOSE имеет плохую репутацию в вопросах безопасности. В исходной спецификации разрешался alg: none (неподписанный JWT, заявляющий "верь мне на слово"); некоторые библиотеки даже поставляли это по умолчанию. Во многих критических статьях подробно описывается путаница между симметричными и асимметричными алгоритмами, атаки с подменой типа ключа (key confusion) и скрытые грабли во всём семействе JOSE. Статья Paragonie "JWT is a Bad Standard" стала каноническим разбором этих проблем.
Больше по размеру и медленнее. JWT, содержащий только ID сессии, весит больше сессионной cookie, требует парсинга и проверки подписи на каждом запросе, не давая при этом никаких преимуществ.
Почему к нему всё равно прибегают
- Это вариант по умолчанию во многих руководствах по JavaScript и Node.js.
- Концепция "stateless" выглядит привлекательно для горизонтального масштабирования - пока не понимаешь, что Redis или Postgres для отзыва сессий всё равно понадобятся.
- Разработчикам мобильных приложений и SPA внушили, что cookie устарели, а Bearer-токены - современный стандарт. Это не так: cookie (в частности с флагами
HttpOnly; Secure; SameSite=Strict|Lax) остаются правильным инструментом для аутентификации в браузере и отлично подходят для SPA. - Микросервисные архитектуры, где один сервис выполняет аутентификацию, а остальные проверяют токен без общего хранилища сессий. JWT здесь действительно работает, но это сценарий взаимодействия между сервисами, а не управления сессиями пользователей.
Где JWT (или paseto) действительно к месту
Те же критики сходятся во мнении, что подписанные короткоживущие токены имеют вполне легитимные сферы применения - и все они лежат в диапазоне времени жизни около 5 минут:
- Транспорт для single-sign-on (паттерн Google) - вход на хосте A, передача JWT хосту B как одноразового подтверждения, после чего хост B создаёт собственную сессию.
- Ссылки для сброса пароля и подтверждения адреса электронной почты.
- Токены возможностей / capability tokens (подписанные URL для скачивания, подписанные токены действий).
- Аутентификация между сервисами (service-to-service) в микросервисах.
Но даже для этих задач paseto сейчас считается более удачно спроектированной альтернативой.
Не усугубляйте антипаттерн с помощью localStorage
Хранение JWT в localStorage или sessionStorage превращает любую XSS-уязвимость в прямую утечку учётных данных. JS в браузере имеет доступ ко всем записям. До cookie с флагом HttpOnly скрипты JS добраться не могут вовсе, поэтому такие cookie переживают XSS. Статья Randall Degges 2018 года, на которую ссылается gist от samsch, - стандартный источник по этому вопросу.
См. также
- cookie-session-auth - рекомендуемый паттерн для сессий в браузере
- paseto - более грамотно спроектированная спецификация короткоживущих токенов для случаев, когда они действительно нужны
- samsch-jwt-auth-gist - краткая версия аргументации в виде gist