Пошук уроків, статей та іншого контенту
З’ясуємо, як браузер зберігає cookies, які бувають атрибути та як налаштувати їх для захисту даних.
Cookie — це невеликий фрагмент текстових даних, який браузер зберігає для певного сайту.
Сервер надсилає cookie у відповіді за допомогою заголовка Set-Cookie:
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=LaxПісля цього браузер зберігає cookie та, коли виконує наступні запити до відповідного сайту, додає їх у заголовок Cookie:
Cookie: sessionId=abc123Cookie часто використовують для:
ідентифікації користувача;
збереження ідентифікатора сесії;
налаштувань сайту;
тимчасового збереження невеликих значень.
Cookie не є сховищем для великих обсягів даних. Зазвичай сервер зберігає дані сесії у себе, а в cookie надсилає лише випадковий ідентифікатор цієї сесії.
Життєвий цикл cookie виглядає так:
Клієнт надсилає запит до сервера.
Сервер додає до відповіді заголовок Set-Cookie.
Браузер зберігає cookie.
Під час наступного відповідного запиту браузер автоматично додає cookie до заголовка Cookie.
Сервер читає значення cookie та використовує його.
Браузер не надсилає cookie на кожен можливий сайт. Він перевіряє атрибути cookie, зокрема:
домен;
шлях;
тип запиту;
протокол;
правила міжсайтової передачі.
Cookie надсилаються автоматично. JavaScript у браузері не потрібно додатково додавати їх до звичайного HTTP-запиту.
Атрибути записуються після пари ім’я=значення і розділяються крапкою з комою:
Set-Cookie: sessionId=abc123; Max-Age=3600; Path=/; HttpOnly; Secure; SameSite=LaxMax-AgeВизначає час життя cookie у секундах:
Set-Cookie: theme=dark; Max-Age=3600У цьому прикладі cookie буде чинною приблизно одну годину.
Значення Max-Age=0 або від’ємне значення використовують для видалення cookie.
ExpiresВизначає точну дату завершення дії cookie:
Set-Cookie: theme=dark; Expires=Wed, 31 Dec 2026 23:59:59 GMTЯкщо задані і Max-Age, і Expires, перевага зазвичай надається Max-Age.
Якщо час життя не вказано, cookie зазвичай є сесійною: браузер видаляє її після завершення сесії браузера. Точна поведінка може залежати від налаштувань браузера.
DomainВизначає домен, для якого cookie доступна:
Set-Cookie: sessionId=abc123; Domain=example.comЯкщо Domain не вказано, cookie є host-only: вона доступна лише тому хосту, який її встановив.
Без потреби не слід задавати широкий Domain. Наприклад, cookie для example.com може передаватися також піддоменам. Якщо один із піддоменів менш захищений, це збільшує ризик викрадення або підміни cookie.
PathВизначає, для яких шляхів cookie надсилатиметься:
Set-Cookie: cart=123; Path=/shopТака cookie надсилатиметься для URL на кшталт:
/shop;
/shop/products;
/shop/checkout.
Для cookie сесії зазвичай використовують:
Path=/Path обмежує надсилання cookie, але не є повноцінним механізмом ізоляції безпеки між частинами одного сайту.
SecureCookie з атрибутом Secure браузер надсилає лише через HTTPS:
Set-Cookie: sessionId=abc123; SecureЦе захищає cookie від передавання через незашифроване HTTP-з’єднання.
У production-додатках cookie сесії майже завжди повинна мати Secure. Під час локальної розробки через звичайний http://localhost цей атрибут може завадити браузеру надсилати cookie, тому локальну поведінку потрібно враховувати окремо.
Secure не замінює HTTPS для всього сайту. Потрібно налаштувати HTTPS-сервер і перенаправляти HTTP-запити на HTTPS.
HttpOnlyCookie з атрибутом HttpOnly недоступна для JavaScript через document.cookie:
Set-Cookie: sessionId=abc123; HttpOnlyВодночас браузер продовжує автоматично надсилати її серверу.
Цей атрибут особливо важливий для ідентифікаторів сесії. Якщо на сторінці з’явиться шкідливий JavaScript-код через XSS-вразливість, він не зможе просто прочитати таку cookie через document.cookie.
HttpOnly не забороняє JavaScript виконувати запити, у яких браузер автоматично додасть cookie. Тому цей атрибут зменшує ризик крадіжки cookie, але не усуває всі XSS-ризики.
SameSiteSameSite визначає, чи можна надсилати cookie під час міжсайтових запитів.
SameSite=StrictCookie надсилається лише у найбільш обмежених сценаріях на тому самому сайті:
Set-Cookie: sessionId=abc123; SameSite=StrictЦе сильний захист від міжсайтових запитів, але користувач може зіткнутися з неочікуваною поведінкою під час переходу на сайт із зовнішнього ресурсу.
SameSite=LaxCookie надсилається у більшості звичайних навігацій верхнього рівня, але обмежується для багатьох міжсайтових запитів:
Set-Cookie: sessionId=abc123; SameSite=LaxЦе поширений компроміс для cookie сесії.
SameSite=NoneCookie може надсилатися у міжсайтових контекстах:
Set-Cookie: sessionId=abc123; SameSite=None; SecureДля SameSite=None обов’язково потрібен атрибут Secure.
Найчастіше сервер не зберігає в cookie такі дані:
email=user@example.com
role=admin
isAuthenticated=trueКлієнт може змінити значення cookie. Якщо сервер без перевірки довірятиме таким даним, користувач зможе підробити свою роль або статус входу.
Замість цього сервер створює випадковий ідентифікатор:
sessionId=випадкове_значенняА на сервері зберігає відповідність:
ідентифікатор сесії → дані користувачаПід час кожного запиту сервер:
читає sessionId;
знаходить відповідну сесію;
перевіряє, чи вона чинна;
отримує дані користувача.
Приклад простого HTTP-сервера на Node.js:
const http = require('node:http');
const crypto = require('node:crypto');
const sessions = new Map();
const isProduction = process.env.NODE_ENV === 'production';
function parseCookies(cookieHeader = '') {
const cookies = {};
for (const part of cookieHeader.split(';')) {
const separatorIndex = part.indexOf('=');
if (separatorIndex === -1) {
continue;
}
const name = part.slice(0, separatorIndex).trim();
const value = part.slice(separatorIndex + 1).trim();
try {
cookies[name] = decodeURIComponent(value);
} catch {
// Пропускаємо пошкоджене значення cookie.
}
}
return cookies;
}
function createSessionCookie(sessionId) {
const attributes = [
`sessionId=${encodeURIComponent(sessionId)}`,
'HttpOnly',
'Path=/',
'Max-Age=3600',
'SameSite=Lax'
];
// У production cookie передається лише через HTTPS.
if (isProduction) {
attributes.push('Secure');
}
return attributes.join('; ');
}
function createExpiredSessionCookie() {
const attributes = [
'sessionId=',
'HttpOnly',
'Path=/',
'Max-Age=0',
'SameSite=Lax'
];
if (isProduction) {
attributes.push('Secure');
}
return attributes.join('; ');
}
const server = http.createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
const cookies = parseCookies(req.headers.cookie);
if (url.pathname === '/login' && req.method === 'POST') {
const sessionId = crypto.randomBytes(32).toString('hex');
// У реальному застосунку тут перевіряють логін і пароль.
sessions.set(sessionId, {
userId: 42,
createdAt: Date.now()
});
res.writeHead(200, {
'Content-Type': 'text/plain; charset=utf-8',
'Set-Cookie': createSessionCookie(sessionId)
});
res.end('Вхід виконано');
return;
}
if (url.pathname === '/profile' && req.method === 'GET') {
const session = cookies.sessionId
? sessions.get(cookies.sessionId)
: undefined;
if (!session) {
res.writeHead(401, {
'Content-Type': 'text/plain; charset=utf-8'
});
res.end('Потрібно увійти');
return;
}
res.writeHead(200, {
'Content-Type': 'application/json; charset=utf-8'
});
res.end(JSON.stringify({
message: 'Профіль користувача',
userId: session.userId
}));
return;
}
if (url.pathname === '/logout' && req.method === 'POST') {
if (cookies.sessionId) {
sessions.delete(cookies.sessionId);
}
res.writeHead(200, {
'Content-Type': 'text/plain; charset=utf-8',
// Для видалення потрібні ті самі Path та Domain, що й під час створення.
'Set-Cookie': createExpiredSessionCookie()
});
res.end('Вихід виконано');
return;
}
res.writeHead(404, {
'Content-Type': 'text/plain; charset=utf-8'
});
res.end('Сторінку не знайдено');
});
server.listen(3000, () => {
console.log('Сервер запущено на http://localhost:3000');
});У цьому прикладі:
ідентифікатор сесії створюється за допомогою криптографічно стійких випадкових байтів;
дані сесії зберігаються на сервері в Map;
cookie має HttpOnly;
cookie діє для всього сайту завдяки Path=/;
cookie автоматично видаляється через одну годину;
SameSite=Lax обмежує міжсайтове надсилання;
у production додається Secure;
під час виходу сервер видаляє сесію та cookie.
Запустити приклад можна командою:
node server.jsДля тестування маршруту входу можна використати інструмент командного рядка:
curl -i -c cookies.txt -X POST http://localhost:3000/login
curl -i -b cookies.txt http://localhost:3000/profile
curl -i -b cookies.txt -c cookies.txt -X POST http://localhost:3000/logoutПрапорець -c зберігає отримані cookies у файл, а -b надсилає їх у наступному запиті.
Cookie видаляють не спеціальною командою браузера, а встановленням тієї самої cookie з минулим часом життя:
Set-Cookie: sessionId=; Max-Age=0; Path=/Під час видалення повинні збігатися атрибути, які визначають область cookie, зокрема:
ім’я;
Path;
Domain, якщо його було задано.
Наприклад, cookie з Path=/account не видалиться коректно заголовком із Path=/.
Для cookie, яка містить ідентифікатор сесії, зазвичай використовують таку конфігурацію:
Set-Cookie: sessionId=random-value; Path=/; HttpOnly; Secure; SameSite=LaxПризначення атрибутів:
HttpOnly — забороняє читати cookie через JavaScript;
Secure — дозволяє надсилати cookie лише через HTTPS;
SameSite=Lax — обмежує міжсайтове надсилання;
Path=/ — робить cookie доступною для всіх маршрутів сайту.
Якщо cookie потрібна лише певному маршруту, можна використати вужчий Path. Наприклад:
Set-Cookie: checkoutId=abc123; Path=/checkout; HttpOnly; Secure; SameSite=LaxCookie без HttpOnly можна прочитати в браузері:
console.log(document.cookie);Результат міститиме лише доступні JavaScript cookies у форматі:
theme=dark; language=ukАтрибути cookie через document.cookie не відображаються.
Cookie з HttpOnly у document.cookie не з’являються. Це очікувана поведінка:
Set-Cookie: sessionId=abc123; HttpOnlyЯкщо клієнтський код виконує запит до того самого сайту, браузер зазвичай сам додасть відповідні cookies. Не потрібно переносити значення сесії в localStorage або вручну додавати його до кожного запиту.
Паролі не можна зберігати в cookie. Cookie може бути викрадена, скопійована або випадково потрапити в журнал запитів.
Краще зберігати лише випадковий ідентифікатор сесії.
HttpOnly для сесійної cookieЯкщо cookie сесії доступна JavaScript-коду, XSS-вразливість може дозволити прочитати її значення.
Для сесійних cookies використовуйте HttpOnly.
Secure у productionCookie без Secure може бути передана через HTTP. У production потрібно використовувати HTTPS і Secure.
SameSite=None без SecureБраузери очікують, що cookie з SameSite=None також матиме Secure. Без цього cookie може бути відхилена.
DomainНе встановлюйте Domain для всього основного домену без потреби. Cookie може почати надсилатися піддоменам, які не повинні мати до неї доступу.
PathДля видалення потрібно повторити область дії cookie:
Set-Cookie: sessionId=; Max-Age=0; Path=/Якщо початкова cookie мала інший Path, браузер може залишити її.
Користувач може змінити cookie, якщо вона не захищена криптографічним підписом або серверною сесією. Не використовуйте значення на кшталт role=admin як єдине джерело прав доступу.
Права потрібно визначати на сервері після перевірки сесії.
Cookie — це дані, які браузер зберігає та автоматично надсилає серверу.
Сервер створює cookie через заголовок Set-Cookie.
Браузер надсилає їх назад у заголовку Cookie.
Max-Age та Expires визначають час життя.
Domain і Path обмежують область дії cookie.
Secure дозволяє надсилати cookie лише через HTTPS.
HttpOnly забороняє доступ до cookie з JavaScript.
SameSite обмежує міжсайтове надсилання cookie.
Для сесії краще зберігати в cookie випадковий ідентифікатор, а не дані користувача.
Для безпечної cookie сесії зазвичай використовують HttpOnly, Secure, SameSite=Lax і відповідний Path.