🍪 Cookies и клиентское хранилище

💡 Основная идея

Все три флага безопасности cookie звучат так, будто “защищают от XSS”, если читать по диагонали — но каждый из них защищает от разных атак. Путать их — самая распространённая ошибка здесь.


Cookies

Небольшие пары ключ/значение, которые браузер автоматически прикрепляет к подходящим запросам через заголовок Cookie. Флаги — HttpOnly, Secure, SameSite, Path, Domain, Max-Age — управляют безопасностью и областью действия.

Три флага, три разные угрозы

  • HttpOnly → XSS. Не мешает вредоносному скрипту выполниться — мешает этому скрипту прочитать конкретно эту cookie через document.cookie. XSS всё равно выполняется; он просто не может украсть защищённую cookie.
  • Secure → перехват в сети / MITM. Cookie никогда не отправляется по обычному HTTP, даже если пользователь случайно попал на HTTP-версию HTTPS-сайта.
  • SameSite → CSRF (Cross-Site Request Forgery). Мешает другому сайту заставить браузер прикрепить вашу cookie к запросу против вашего домена.
    • Strict — cookie никогда не отправляется с межсайтовым запросом.
    • Lax — отправляется только при top-level навигации (клик по ссылке), но не с форм/fetch/XHR с другого сайта.
    • None — отправляется всегда, но обязательно в паре с Secure.

Для токена сессии HttpOnly cookie обычно безопаснее, чем localStorage: localStorage полностью читаем любым JS на странице, включая вредоносный код, внедрённый через XSS — HttpOnly cookie же вообще невидима для JS.

setCookie(event, "session", sessionId, {
  httpOnly: true,
  secure: true,
  sameSite: "lax",
  path: "/",
  maxAge: 60 * 60 * 24 * 7,
})

cookies vs localStorage vs sessionStorage

КритерийcookieslocalStoragesessionStorage
Время жизнипо умолчанию до закрытия браузера; Max-Age/Expires для фиксированного сроканавсегда, пока явно не удаленопока не закрыта вкладка (переживает перезагрузку страницы)
Видимость на сервереотправляется автоматически с каждым HTTP-запросом (заголовок Cookie)только клиент, JS должен отправлять явнотолько клиент, JS должен отправлять явно
Размер~4 КБ~5-10 МБ~5-10 МБ
APIdocument.cookie (сырая строка, которую нужно самому парсить).setItem()/.getItem()/.removeItem().setItem()/.getItem()/.removeItem()
Флаги безопасностиHttpOnly, Secure, SameSiteнет — полностью доступно из JSнет — полностью доступно из JS
Использовать длятокена сессии, который должен уходить на сервер с каждым запросомданных, которые должны сохраняться между визитами (настройка темы, долгоживущий черновик)данных, привязанных к одной вкладке/сессии (черновик формы, шаг мастера)

За пределами cookies: другое клиентское хранилище

  • localStorage / sessionStorage — синхронные, небольшие, только строки (сериализуйте объекты через JSON.stringify/parse). Не источник истины — полностью видимы и редактируемы пользователем.
  • IndexedDB — асинхронное, работает с большими структурированными данными, правильный выбор, когда ограничения localStorage по размеру/синхронности становятся проблемой.
  • Cache API — работает в паре с service worker для оффлайн-кеширования и кеширования ассетов.

Практические заметки: версионируйте форму хранимых данных (settings:v2) и обрабатывайте миграции; слушайте событие storage для синхронизации состояния между вкладками; учитывайте, что window может быть недоступен во время SSR.

HTTP & Network CORS — credentials и кросс-доменные cookies browser