🍪 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
| Критерий | cookies | localStorage | sessionStorage |
|---|---|---|---|
| Время жизни | по умолчанию до закрытия браузера; Max-Age/Expires для фиксированного срока | навсегда, пока явно не удалено | пока не закрыта вкладка (переживает перезагрузку страницы) |
| Видимость на сервере | отправляется автоматически с каждым HTTP-запросом (заголовок Cookie) | только клиент, JS должен отправлять явно | только клиент, JS должен отправлять явно |
| Размер | ~4 КБ | ~5-10 МБ | ~5-10 МБ |
| API | document.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