
Запись cookie из JavaScript: функция SetCookie и её атрибуты
Cookie из JavaScript записывается одной строкой, но у этой строки есть формат и набор атрибутов. Собираем аккуратную функцию SetCookie и разбираем, почему cookie иногда «не сохраняется».
Как устроен document.cookie
Свойство document.cookie выглядит как обычная строка, но работает необычно. При чтении оно возвращает все доступные странице cookie одной строкой через точку с запятой. При записи присваивание не заменяет весь набор, а добавляет или обновляет ровно одну cookie, описанную в правой части.
Строка записи состоит из пары «имя=значение» и необязательных атрибутов: срока жизни, пути, домена и флагов безопасности. Если атрибуты не указаны, получится сессионная cookie, которая живёт до закрытия браузера и видна только страницам из того же каталога, что и текущая. Последнее часто становится сюрпризом: cookie, записанная со страницы /shop/cart.aspx, не видна на /default.aspx.
Функция SetCookie
Ниже вариант, который подходит для большинства задач. Срок жизни задаётся в днях, путь по умолчанию равен корню сайта, чтобы cookie видели все страницы. Значение кодируется через encodeURIComponent: точка с запятой, запятая или пробел внутри значения иначе обрежут его или испортят соседние атрибуты.
Дату окончания формируем через toUTCString. Метод toGMTString, который встречается в старых примерах, давно объявлен устаревшим, хотя возвращает строку того же формата.
// Записывает cookie. days: срок жизни в днях, без него cookie сессионная
function SetCookie(name, value, days, path) {
var parts = [encodeURIComponent(name) + '=' + encodeURIComponent(value)];
if (days) {
var exp = new Date();
exp.setTime(exp.getTime() + days * 24 * 60 * 60 * 1000);
parts.push('expires=' + exp.toUTCString());
}
parts.push('path=' + (path || '/'));
if (location.protocol === 'https:') parts.push('secure');
parts.push('samesite=lax');
document.cookie = parts.join('; ');
}
// SetCookie('theme', 'blue', 30);Атрибуты и что они меняют
Каждый атрибут влияет на то, когда браузер отправит cookie серверу и увидит ли её скрипт. Ошибка в любом из них приводит к ситуации, когда запись вроде бы прошла, а прочитать значение не получается.
- expires или max-age: срок жизни. max-age задаётся в секундах и удобнее, но Internet Explorer до девятой версии его не понимал, поэтому в legacy-коде встречается только expires;
- path: префикс URL, для которого cookie видна. Значение «/» делает её общей для сайта;
- domain: позволяет поделиться cookie с поддоменами, например domain=example.com для www и shop. Чужой домен указать нельзя, браузер такую запись проигнорирует;
- secure: cookie уходит только по HTTPS;
- samesite: правило отправки при переходах с других сайтов. Lax подходит для настроек интерфейса, None требует обязательного secure.
Чтение и удаление в пару к записи
Отдельного API для чтения нет: строку document.cookie приходится разбирать самостоятельно. Делим её по точке с запятой, у каждой части отрезаем пробелы и сравниваем имя. Значение декодируем той же парой функций, которой кодировали.
Удалить cookie можно только одним способом: записать её заново с датой в прошлом. Путь и домен при этом должны совпадать с исходными.
function ReadCookie(name) {
var key = encodeURIComponent(name) + '=';
var items = document.cookie.split(';');
for (var i = 0; i < items.length; i++) {
var c = items[i].replace(/^\s+/, '');
if (c.indexOf(key) === 0) {
return decodeURIComponent(c.substring(key.length));
}
}
return null; // не найдена
}
function DeleteCookie(name, path) {
document.cookie = encodeURIComponent(name) +
'=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=' + (path || '/');
}Ограничения и безопасность
Cookie отправляются серверу с каждым запросом к домену, включая картинки и стили. Поэтому хранить в них большие объёмы данных невыгодно: каждая лишняя сотня байт умножается на число запросов. Браузеры ограничивают размер одной cookie примерно четырьмя килобайтами, а количество на домен несколькими десятками.
Скрипт видит не все cookie. Если сервер выставил флаг HttpOnly, как это делает ASP.NET для cookie сессии и аутентификации форм, document.cookie её не покажет. Это защита от кражи сессии через XSS, а не ошибка.
- не храните в cookie пароли, номера карт и персональные данные;
- значение, записанное в браузере, может подменить пользователь, поэтому на сервере его нужно проверять;
- для настроек интерфейса (тема, свёрнутая панель, язык) cookie подходят хорошо.
Связка с ASP.NET
Cookie, записанную скриптом, сервер читает обычным образом: Request.Cookies(«theme»).Value в VB.NET. Значение придёт в том виде, в каком его закодировал encodeURIComponent, поэтому на сервере его стоит пропустить через HttpUtility.UrlDecode. Обратное тоже работает: cookie, выставленную через Response.Cookies без HttpOnly, можно прочитать функцией ReadCookie.
Частая причина расхождений: сервер ставит cookie с путём приложения, например /app, а скрипт пишет с путём «/». В итоге у браузера две cookie с одним именем, и какая из них придёт первой, зависит от длины пути.
Как это делают сегодня
Вопросы и ответы
Почему cookie не видна на другой странице сайта?
Вероятно, она записана без атрибута path и привязалась к каталогу текущей страницы. Указывайте path=/ при записи, и cookie станет доступна всему сайту.
Почему скрипт не видит cookie сессии ASP.NET?
Сервер выставляет её с флагом HttpOnly, и браузер скрывает такие cookie от JavaScript. Это защитная мера, обходить её не нужно.
Можно ли хранить в cookie объект?
Можно сериализовать его через JSON.stringify и закодировать encodeURIComponent, но размер ограничен примерно четырьмя килобайтами. Для крупных данных лучше localStorage.