Как безопасно хранить access и refresh токены в браузере в 2026 году

Вопрос хранения токенов в браузере — это вечный спор в сообществе фронтенд-разработчиков. Одни топят за localStorage, другие — за HttpOnly cookies, третьи предлагают хранить всё в памяти приложения. Проблема в том, что идеального, абсолютно безопасного места не существует. Любое решение — это всегда компромисс между удобством разработки (DX), пользовательским опытом (UX) и уровнем защищенности.
В этой статье мы разберем, почему старые подходы больше не работают, и как выстроить архитектуру аутентификации, которая будет устойчива к современным векторам атак в 2024 году.
Анатомия проблемы: XSS против CSRF
Прежде чем выбирать «хранилище», нужно понять, от чего мы защищаемся. В веб-безопасности есть два главных врага:
- XSS (Cross-Site Scripting) — когда злоумышленник внедряет свой JS-код на вашу страницу. Если ваш токен лежит в
localStorageили обычномcookie, скрипт просто прочитает его черезdocument.cookieилиlocalStorage.getItem()и отправит на сторонний сервер. - CSRF (Cross-Site Request Forgery) — когда вредоносный сайт заставляет браузер пользователя отправить запрос на ваш сервер. Если вы используете куки, браузер прикрепит их автоматически, и сервер примет запрос за легитимный.
Главный парадокс в том, что защита от XSS (перенос токенов в HttpOnly куки) делает нас уязвимыми для CSRF. И наоборот.
Почему localStorage — это «смертный грех» для Access-токенов
Многие туториалы до сих пор учат складывать токены в localStorage. С точки зрения разработчика это удобно: один вызов API, сохранили строку, достали её при каждом запросе. Но с точки зрения безопасности — это катастрофа.
localStorage доступен любому JavaScript-коду, запущенному в контексте вашего домена. Это значит, что любая скомпрометированная библиотека из вашего node_modules или сторонний рекламный скрипт могут мгновенно украсть ваш Access-токен. Если срок жизни токена большой, злоумышленник получит полный доступ к аккаунту пользователя на долгое время.
Современная стратегия: Разделение ответственности
В 2026 году стандартом индустрии является использование связки из короткоживущего Access Token и долгоживущего Refresh Token. Но секрет не в самих токенах, а в том, как и где они живут.
1. Access Token: Хранение в памяти (In-Memory)
Самый безопасный способ хранения Access-токена — это обычная переменная в состоянии вашего приложения (например, в Redux, Vuex, или просто в замыкании сервиса аутентификации).
Почему это работает:
-
- Переменная в памяти недоступна для XSS-атак через чтение хранилища.
-
- При обновлении страницы токен стирается, что кажется минусом, но именно это делает его безопасным.
Как реализовать процесс:
-
- После успешного логина сервер присылает Access-токен в теле ответа (JSON).
-
- Фронтенд сохраняет его в переменную (state).
-
- Для каждого запроса токен добавляется в заголовок
Authorization: Bearer <token>.
- Для каждого запроса токен добавляется в заголовок
-
- При перезагрузке страницы приложение делает запрос на
/refresh, чтобы получить новый Access-токен.
- При перезагрузке страницы приложение делает запрос на
2. Refresh Token: Защищенные куки (HttpOnly, Secure, SameSite)
Поскольку Access-токен живет коротко (например, 15 минут), нам нужен механизм его обновления без повторного ввода пароля. Для этого используется Refresh-токен. Его нельзя хранить в памяти (он пропадет при перезагрузке) и нельзя в localStorage (его украдут).
Единственный приемлемый вариант — HttpOnly Cookie.
Настройки куки, которые должны быть обязательно:
-
HttpOnly: JavaScript не может прочитать эту куку. Это полностью отсекает XSS-кражу.
-
Secure: Кука передается только по HTTPS.
-
SameSite=Strict(илиLax): Запрещает отправку куки при кросс-доменных запросах, что практически полностью нивелирует риск CSRF.
Архитектура «Безопасного обновления» (The Refresh Flow)
Вот как выглядит идеальный цикл жизни сессии в современном приложении:
- Авторизация: Пользователь вводит логин/пароль $\rightarrow$ Сервер проверяет данные $\rightarrow$ Возвращает Access-токен в JSON-ответе и устанавливает Refresh-токен в
Set-Cookieс флагамиHttpOnly; Secure; SameSite=Strict. - Работа: Приложение использует Access-токен из памяти для запросов.
- Истечение: Через 15 минут сервер возвращает
401 Unauthorized. - Обновление: Фронтенд перехватывает 401 ошибку (через интерцепторы axios или fetch) и делает запрос на
/refresh. - Ротация: Сервер видит Refresh-токен в куках, проверяет его, генерирует новую пару (новый Access и новый Refresh) и возвращает их.
- Безопасность: Старый Refresh-токен аннулируется (это называется Refresh Token Rotation). Если злоумышленник всё же украл Refresh-токен и попытался его использовать, сервер заметит повторное использование старого токена и мгновенно заблокирует всю сессию пользователя.
Продвинутые методы защиты: BFF (Backend For Frontend)
Если вы строите высоконагруженное или корпоративное приложение с повышенными требованиями к безопасности, рассмотрите паттерн BFF.
Суть в том, что браузер вообще не видит никаких JWT. Вместо этого:
- Создается промежуточный сервер (BFF), который общается с основным API.
- BFF создает традиционную сессию с использованием зашифрованной куки.
- Все токены (Access и Refresh) хранятся на стороне BFF в защищенном хранилище или кэше (например, в Redis).
- Браузер общается с BFF через сессионную куку, а BFF «подмешивает» нужные токены перед отправкой запроса в основной микросервис.
Это полностью убирает риск утечки JWT с клиента, так как токены физически не покидают пределы серверного периметра.
Чек-лист для разработчика (Резюме)
Чтобы ваше приложение не стало легкой мишенью, проверьте выполнение следующих пунктов:
- [ ] Access-токен НЕ хранится в
localStorageилиsessionStorage. - [ ] Refresh-токен хранится в куках с флагом
HttpOnly. - [ ] Установлен флаг
SameSite=Strictдля предотвращения CSRF. - [ ] Реализована ротация Refresh-токенов (каждый Refresh используется один раз).
- [ ] Срок жизни Access-токена минимален (от 5 до 15 минут).
- [ ] Реализован механизм
Logout, который удаляет куку на сервере и очищает память на клиенте. - [ ] Используется Content Security Policy (CSP) для минимизации рисков XSS.
Заключение
Безопасность — это не конечная точка, а процесс. В 2024 году мы уходим от простых решений в сторону многослойной защиты. Хранение Access-токена в памяти в сочетании с ротируемыми HttpOnly куками для обновления — это золотой стандарт для большинства SPA (React, Vue, Angular).
Помните: чем меньше данных вы доверяете браузеру, тем спокойнее спите по ночам. Если ваш проект критически важен — переходите на архитектуру BFF. Если же вы делаете обычный сервис — следуйте схеме «Память + HttpOnly Cookies», и вы будете защищены от 99% типичных атак.

