Сессии или JWT: как реализовать авторизацию в веб-приложении
Практика3 минРедакция Innovialabs
Коротко
Для обычного веб-приложения надёжный выбор по умолчанию — серверные сессии в httpOnly cookie; JWT с refresh-токенами оправдан для мобильных клиентов, нескольких сервисов и внешних API.
Короткий ответ
- Сессия: после входа сервер создаёт запись в хранилище (БД, Redis) и отдаёт браузеру случайный ID в cookie. На каждый запрос сервер находит сессию по ID.
- JWT: сервер выдаёт подписанный токен с данными пользователя и сроком жизни. Проверка — по подписи, без обращения к хранилищу.
Если у вас один сайт и свой бэкенд, начинайте с сессий в httpOnly cookie. Они проще, их легко отозвать, и в них сложнее ошибиться.
Сравнение
| Cookie-сессия | JWT | |
|---|---|---|
| Где состояние | На сервере | Внутри токена |
| Проверка запроса | Поиск в хранилище | Проверка подписи |
| Выход и отзыв | Удалить запись — и всё | Сложно до истечения срока |
| Мобильные приложения | Возможно, но менее удобно | Естественно |
| Несколько сервисов | Нужно общее хранилище | Каждый проверяет подпись сам |
| Риск ошибок | Ниже | Выше: алгоритмы, срок, хранение |
Как устроен refresh-поток
Главная проблема JWT — его нельзя «погасить» до истечения срока. Поэтому используют пару токенов:
- Access token — короткоживущий, передаётся с каждым запросом.
- Refresh token — долгоживущий, хранится надёжно и нужен только для получения нового access token.
- Когда access истёк, клиент вызывает
/auth/refreshи получает новую пару. - Ротация: при каждом обновлении старый refresh становится недействительным. Если кто-то пытается использовать уже потраченный refresh — это признак кражи, и всю цепочку стоит отозвать.
Обратите внимание: refresh-токены хранятся на сервере, чтобы их можно было отозвать. То есть JWT-схема всё равно возвращается к состоянию на сервере.
Где хранить токены в браузере
- httpOnly, Secure cookie — JavaScript не может прочитать, значит, XSS не украдёт токен напрямую. Нужна защита от CSRF:
SameSite=LaxилиStrictи, для изменяющих запросов, CSRF-токен или проверка Origin. - localStorage — доступен любому скрипту на странице. Одна XSS-уязвимость — и токен утёк. Для долгоживущих токенов не рекомендуется.
- Память приложения — допустимо для короткого access token; refresh при этом держат в httpOnly cookie.
Set-Cookie: sid=8f3c...; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=1209600
Выход и отзыв доступа
- Сессии: удаляете запись — пользователь вышел на всех устройствах или на одном, как решите.
- JWT: удаляете refresh на сервере, а access доживает свой короткий срок. Для мгновенной блокировки нужен denylist или версия токена у пользователя, которую проверяет сервер.
Обязательно отзывайте все сессии при смене пароля и дайте пользователю список активных устройств.
Рекомендуемая архитектура по умолчанию
- Пароли — только в виде хеша медленным алгоритмом (bcrypt, scrypt, Argon2).
- Веб-клиент — серверная сессия, ID в cookie
HttpOnly; Secure; SameSite=Lax. - Новый ID сессии после входа (защита от фиксации сессии).
- Ограничение попыток входа.
- Мобильное приложение или внешние интеграции — короткий access JWT + ротируемый refresh, хранимый на сервере.
- Проверка прав на каждом запросе на сервере, а не только скрытие кнопок в интерфейсе.
Если не хотите писать всё сами, используйте зрелую библиотеку аутентификации для вашего фреймворка или внешнего провайдера идентификации.
Частые ошибки
- JWT в localStorage без срока жизни.
- Чувствительные данные в payload JWT — он подписан, но не зашифрован, его может прочитать кто угодно.
- Приём токенов с
alg: noneили без строгой проверки алгоритма. - Нет механизма выхода «со всех устройств».