Файли cookies («куки») – це невеликі рядки з даними, які зберігаються безпосередньо в браузері. Вони є частиною HTTP протоколу, визначеного специфікацією RFC 6265.
Файли cookies зазвичай встановлюються вебсервером за допомогою заголовка HTTP Set-Cookie
. Потім браузер автоматично додаватиме їх при (майже) кожному запиті до відповідного домену використовуючи заголовок HTTP Cookie
.
Одним з найбільш поширених випадків використання є аутентифікація:
- При вході в систему, сервер використовує відповідь отриману з заголовка HTTP
Set-Cookie
, щоб додати в файл cookie унікальний “ідентифікатор сесії” . - Наступного разу, коли на той самий домен буде відправлено запит, браузер надішле файл cookie використовуючи заголовок HTTP
Cookie
. - Таким чином, сервер знає, хто зробив запит.
Також ми маємо доступ до файлів cookies з браузера, використовуючи властивість document.cookie
.
У файлах cookies та їхніх атрибутах є багато тонкощів. В цьому розділі ми детально їх розглянемо.
Зчитування з document.cookie
Чи зберігає браузер файли cookie з цього сайту? Перевіримо:
// На javascript.info, ми використовуємо Google Analytics для збору статистики,
// тому тут мають бути деякі файли cookies
alert( document.cookie ); // cookie1=value1; cookie2=value2;...
Значення document.cookie
складається з пар name=value
розділених ;
. Кожна пара – це окремий файл cookie.
Щоб знайти певний файл cookie потрібно розділити значення document.cookie
за допомогою ;
, а потім знайти потрібний ключ за його назвою. Для цього можна використовувати як регулярні вирази, так і функції для роботи з масивами.
Залишимо це завдання для самостійної роботи читача. Окрім того, в кінці розділу ви знайдете допоміжні функції для роботи з файлами cookies.
Запис в document.cookie
Ми можемо записувати в document.cookie
. Але це не просто властивості даних, це аксесори (гетери/сетери). Присвоєння цих властивостей обробляється особливим чином.
Запис в document.cookie
оновлює лише вказаний файл cookie, та не чіпатиме решту.
Наприклад, цей виклик встановить файл cookie з іменем user
та значенням John
:
document.cookie = "user=John"; // оновити лише файл cookie під назвою 'user'
alert(document.cookie); // показати всі файли cookies
Якщо ви запустите цей код, то, швидше за все, побачите декілька файлів cookies. Це тому, що операція document.cookie=
оновлює не всі файли cookies, а лише вказаний файли з іменем user
.
Технічно, ім’я та значення можуть містити будь-які символи. Щоб зберегти правильне форматування, вони повинні бути кодовані за допомогою вбудованої функції encodeURIComponent
.
// спеціальні символи (пробіли, кирилиця), потребують кодування
let name = "my name";
let value = "John Smith";
// кодування cookie як my%20name=John%20Smith
document.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value);
alert(document.cookie); // ...; my%20name=John%20Smith
Є декілька обмежень:
- За допомогою
document.cookie
, ви можете встановити/оновити лише один файл cookie. - Пара
name=value
, після кодуванняencodeURIComponent
, не повинна перевищувати 4кБ. Тому ми не можемо зберігати великий обʼєм даних у файлах cookie. - Дозволена сумарна кількість файлів cookie на один домен приблизно 20+, точний ліміт залежить від браузера.
Файли cookies мають ряд атрибутів, деякі з них важливі і повинні бути задані.
Атрибути перераховуються після пари key=value
та розділяються ;
, як показано нижче:
document.cookie = "user=John; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT"
domain
domain=site.com
Домен визначає звідки будуть доступні файли cookie. Проте, на практиці, існують деякі обмеження – ми не можемо вказати будь-який домен.
Не існує способу зробити файли cookie доступними з іншого домену 2-го рівня, тому other.com
ніколи не отримає файл cookie заданий на site.com
.
Це обмеження задля безпеки, воно дозволяє нам зберігати конфіденційні дані в файлах cookie, які будуть доступними лише на одному сайті.
Типово, файли cookie доступні лише на тому домені на якому були встановлені.
Зверніть увагу, що типово файли cookie також не доступні і на піддомені, такому як forum.site.com
.
// якщо задати файл cookie на вебсайті site.com ...
document.cookie = "user=John"
// ...ми не побачимо їх на forum.site.com
alert(document.cookie); // користувача немає
…Проте цю поведінку можна змінити. Якщо ми хочемо дозволити піддоменам на кшталт forum.site.com
отримувати файли cookie задані на site.com
– це також можливо.
Для цього, встановлюючи файл cookie за адресою site.com
, ми повинні явно задати в атрибуті domain
кореневий домен: domain=site.com
. Тоді всі піддомени побачать такий файл cookie.
Наприклад:
// на site.com
// зробити файл cookie доступним на будь-якому піддомені *.site.com:
document.cookie = "user=John; domain=site.com"
// пізніше
// на forum.site.com
alert(document.cookie); // файл cookie user=John існує
Історично склалося так що, domain=.site.com
(з крапкою перед site.com
) спрацює так само, надаючи доступ до файлів cookie з піддоменів. Тепер крапки на початку доменних імен ігноруються, але деякі браузери можуть відмовитися встановлювати файл cookie, що містить такі крапки.
Отже, атрибут domain
робить файли cookie доступними на піддоменах.
path
path=/mypath
URL-префікс адреси повинен бути абсолютним. Для того щоб файли cookie були доступними зі сторінок за цією адресою. Типово, це поточна сторінка.
Якщо в файлі cookie задано path=/admin
, то він видимий на сторінках /admin
та /admin/something
, але не на /home
або /adminpage
.
Зазвичай, щоб зробити файл cookie доступним з усіх сторінок сайту, нам необхідно вказати корінь: path=/
. Якщо цей атрибут не встановлено, типове значення обчислюється за допомогою цього методу.
expires, max-age
Типово, якщо файл cookie не має одного з цих атрибутів, то файл зникає при закриванні браузера/вкладки. Такі файли cookies називаються сесійними (“session cookies”).
Щоб файли cookies могли “пережити” закривання браузера/вкладки, можна встановити значення одного з атрибутів expires
або max-age
. max-age
має перевагу, якщо встановлено обидва значення.
expires=Tue, 19 Jan 2038 03:14:07 GMT
Термін придатності файлу cookie визначає час, коли браузер автоматично видалить його (відповідно до часового поясу браузера).
Дата має бути вказана саме в такому форматі, в часовому поясі GMT. Щоб отримати правильну дату, можна скористатися date.toUTCString
. Наприклад, можемо встановити, що термін придатності файлу cookie закінчується через 1 день:
// +1 день від сьогодні
let date = new Date(Date.now() + 86400e3);
date = date.toUTCString();
document.cookie = "user=John; expires=" + date;
Якщо встановити значенням параметру expires
дату з минулого, то файл cookie видалиться.
max-age=3600
Це альтернатива expires
, який вказує термін придатності cookie в секундах з поточного моменту.
Як встановлено 0 або від’ємне значення, то файл cookie буде видалено:
// файл cookie буде видалено через 1 годину
document.cookie = "user=John; max-age=3600";
// видалити файл cookie (термін придатності закінчується прямо зараз)
document.cookie = "user=John; max-age=0";
secure
secure
Файл cookie повинен передаватися виключно по HTTPS-протоколу.
За замовчуванням, якщо ми створимо файл cookie на http://site.com
, тоді він автоматично з’явиться на https://site.com
та навпаки.
Тобто файли cookie базуються на домені, вони не залежать від протоколів.
В разі якщо цей атрибут задано, то файл cookie створений на https://site.com
не буде доступний на тому ж вебсайті з HTTP-протоколом http://site.com
. Тому якщо файли cookie містять конфіденційні дані, які в жодному разі не мають бути відправлені по незашифрованому HTTP-протоколу, тоді параметр secure
це правильний вибір.
// припустимо, що зараз ми на https://
// створимо захищений файл cookie (доступний лише по HTTPS)
document.cookie = "user=John; secure";
samesite
Це ще один атрибут безпеки. Він створений щоб захищати від так званих XSRF-атак (міжсайтова підміна запиту).
Щоб зрозуміти як він працює та в яких випадках може бути корисним, давайте детальніше розглянемо поняття XSRF-атак.
XSRF-атаки
Уявіть, що ви увійшли в свій обліковий запис на сайті bank.com
. Тобто: у вас є файли cookie з даними аутентифікації від цього сайту. Ваш браузер відправляє їх сайту bank.com
при кожному запиті, для того щоб той розпізнав вас та виконав всі конфіденційні фінансові операції.
Тепер, переглядаючи вебсторінки в іншому вікні, ви випадково потрапили на сайт evil.com
. Цей сайт містить код JavaScript, який відправляє форму <form action="https://bank.com/pay">
на bank.com
з заповненими полями, які ініціюють транзакцію на рахунок хакера.
Браузер відправляє файли cookies щоразу як ви відвідуєте сайт bank.com
, навіть якщо форма була відправлена з evil.com
. Тому банк “впізнає” вас та дійсно виконає платіж.
Така атака називається міжсайтова підміна запиту (Cross-Site Request Forgery, XSRF).
Звісно, справжні банкові системи захищені від цього. У всіх згенерованих сайтом bank.com
формах є спеціальне поле, так званий “токен захисту від XSRF”, який не може бути ані згенерований зловмисним сайтом, ані вилучений з віддаленої сторінки. Зловмисник може спробувати відправити форму туди, але не може отримати дані назад. Сайт bank.com
перевіряє кожну отриману форму на наявність даного токену.
Проте такий захист вимагає більше часу на реалізацію. Нам потрібно переконатися, що кожна форма несе в собі обов’язкове поле з токеном, окрім того потрібно перевіряти всі запити.
Використання атрибуту samesite в файлі cookie
Атрибут samesite
пропонує ще один спосіб захисту від атак, який (в теорії) не вимагає “токен захисту від xsrf”.
В нього є два можливі значення:
samesite=strict
(те саме, щоsamesite
без заданого значення)
Файли cookie з параметром samesite=strict
ніколи не відправляються якщо користувач прийшов ззовні (з іншого сайту).
Іншими словами, якщо користувач перейшов за посиланням зі свого електронного листа або відправив форму з evil.com
, або виконав яку завгодно операцію, що походить з іншого домену, файли cookie не відправляться.
Якщо файл cookie аутентифікації має атрибут samesite=strict
, тоді у XSRF-атаки немає шансу на успіх, тому що запит з сайту evil.com
надходить без файлу cookie. Таким чином, bank.com
не ідентифікує користувача та не здійснить платіж.
Захист доволі надійний. Файли cookie з атрибутом samesite=strict
відправлятимуться тільки якщо операції надходять з bank.com
, наприклад відправлення форми з іншої сторінки домену bank.com
.
Хоча, у цього рішення є дрібний недолік.
Коли користувач переходить за легітимним посилання до bank.com
, як то зі своїх нотаток, то він буде неприємно здивований коли bank.com
не “впізнає” його. Дійсно, в такому випадку файл cookie з атрибутом samesite=strict
не відправляється.
Цю проблему можна вирішити використанням двох файлів cookie: один для загальної ідентифікації, лише для того щоб сказати: “Привіт, Іван”, а інший з параметром samesite=strict
для операцій з даними. Тоді особа, яка перейшла за посиланням поза межами сайту побачить привітання, проте для того щоб другий файл cookie відправився, платіжні операції повинні бути ініційовані з сайту банку.
samesite=lax
(те саме, щоsamesite
без заданого значення)
Спрощене рішення яке так само захищає від XSRF-атак, але не руйнує очікування користувача.
Режим lax
, так само як strict
, забороняє браузеру відправляти файл cookies коли запит приходить не з сайту, але є одне виключення.
Файл cookie з параметром samesite=lax
відправляється лише тоді, коли виконуються обидві умови:
-
Обраний HTTP-метод безпечний (наприклад GET, але не POST)
Повний список безпечних HTTP-методів зібрано в специфікації RFC7231. По суті, безпечними вважаються методи які використовуються для читання, а не для запису даних. Вони не повинні виконувати жодних операцій редагування даних. Перехід за посиланням це завжди метод GET, тобто безпечний.
-
Операція виконує навігацію вищого рівня (змінює URL в адресному полі браузера)
Як правило, ця умова виконується, проте якщо навігація відбувається в
<iframe>
то це не вищий рівень. Також, методи JavaScript для мережевих запитів не виконують навігації, тому вони не підходять.
Тому насправді, все що робить атрибут samesite=lax
– це дозволяє найбільш поширеним операціям (таким як перехід по URL) передавати файли cookie. Наприклад, відкривання вебсайту за посиланням зі своїх нотаток, що повністю задовольняє умовам.
Але будь-яка більш складна операція, як то мережевий запит з іншого сайту чи відправлення форми, втрачає файли cookies.
Якщо вас це влаштовує, тоді використання samesite=lax
скоріше за все не зіпсує враження користувача від взаємодії з вашим сайтом та захистить дані.
В цілому, samesite
– це чудовий параметр.
Є лише один недолік:
samesite
ігнорується (не підтримується) старими версіями браузерів, до 2017 року і раніше.
Тому, якщо для забезпечення захисту, покладатися виключно на атрибут samesite
, то старі браузери залишаться вразливими.
Звісно, ми можемо використовувати samesite
разом з іншими засобами безпеки, такими як “токен захисту від xsrf”, щоб додати ше один рівень захисту, а потім в майбутньому, коли старі браузери вимруть, ми зможемо відмовитися від токенів.
httpOnly
Цей атрибут не має нічого спільного з JavaScript, але ми повинні згадати його заради повноти картини.
Вебсервер використовує заголовок Set-Cookie
щоб задати файли cookie. Також він може встановити атрибут httpOnly
.
Даний атрибут забороняє будь-який доступ до файлів cookie з JavaScript. Ми не можемо бачити чи маніпулювати файлами cookie користуючись document.cookie
.
Його використовують як запобіжний метод, щоб захистити від деяких атак, коли хакер вживляє на сторінку свій власний код JavaScript та чекає поки користувач зайде на таку сторінку. За ідеальних умов це взагалі неможливо, хакери не повинні мати можливості проникнути своїм кодом на наш сайт, але в коді можуть бути помилки, які все ж дозволяють їм це зробити.
Зазвичай, якщо таке трапляється, і користувач все ж зайшов на вебсторінку с хакерським кодом JavaScript, тоді код виконується та отримує доступ до команди document.cookie
, яка містить аутентифікаційну інформацію. Це погано.
Проте якщо файл cookie має параметр httpOnly
, то в такому разі document.cookie
не бачить його, тому файл в безпеці.
Додаток: Функції файлів cookie
Тут наведено невелику підбірку функцій для роботи з файлами cookie, більш зручних аніж ручна модифікація document.cookie
.
Існує безліч бібліотек для роботи з файлами cookie, тому ці функції тут скоріше в демонстраційних цілях. Але повністю робочі.
getCookie(name)
Найкоротший шлях щоб отримати доступ до файлів cookie це використання регулярних виразів.
Функція getCookie(name)
повертає файл cookie з заданим іменем name
:
// повертає файл cookie з заданим іменем
// або undefined якщо нічого не знайдено
function getCookie(name) {
let matches = document.cookie.match(new RegExp(
"(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)"
));
return matches ? decodeURIComponent(matches[1]) : undefined;
}
Тут динамічно генерується new RegExp
який задовольняє ; name=<value>
.
Зверніть увагу, що значення файлу cookie закодоване, тому getCookie
використовує вмонтовану функцію decodeURIComponent
щоб розшифрувати його.
setCookie(name, value, attributes)
Встановлює файл cookie з заданим ім’ям name
, значенням value
, та типовим параметром path=/
(можна редагувати, щоб додати інші типові значення):
function setCookie(name, value, attributes = {}) {
attributes = {
path: '/',
// за потреби додайте інші типові значення
...attributes
};
if (attributes.expires instanceof Date) {
attributes.expires = attributes.expires.toUTCString();
}
let updatedCookie = encodeURIComponent(name) + "=" + encodeURIComponent(value);
for (let attributeKey in attributes) {
updatedCookie += "; " + attributeKey;
let attributeValue = attributes[attributeKey];
if (attributeValue !== true) {
updatedCookie += "=" + attributeValue;
}
}
document.cookie = updatedCookie;
}
// Приклад використання:
setCookie('user', 'John', {secure: true, 'max-age': 3600});
deleteCookie(name)
Щоб видалили файли cookie, ми можемо викликати функцію вказавши від’ємне значення в параметрі max-age
:
function deleteCookie(name) {
setCookie(name, "", {
'max-age': -1
})
}
Зауважте: коли ми оновлюємо чи видаляємо файли cookie, ми повинні використовувати ті самі адресу та доменне ім’я, що і при встановленні.
Все разом: cookie.js.
Додаток: Сторонні файли cookies
Файли cookie називаються “сторонніми”, якщо вони розміщені з домену який відрізняється від того, до якого належить поточна сторінка.
Наприклад:
-
Сторінка розміщена на
site.com
завантажує банер з іншого сайту:<img src="https://ads.com/banner.png">
. -
Разом з банером, віддалений сервер на
ads.com
може встановити заголовокSet-Cookie
зі значеннямid=1234
взятим з файлу cookie. Створені файли cookie походять з доменуads.com
та будуть видимі лише зads.com
: -
Наступного разу коли
ads.com
відправить запит на доступ, віддалений сервер отримаєid
з файлу cookie та розпізнає користувача: -
Іще важливіше те, що коли користувач переходить з
site.com
до іншого сайтуother.com
, на якому також є банер, тодіads.com
отримує файли cookie, так наче вони насправді належатьads.com
і таким чином розпізнає відвідувача та відстежить його, коли той переміщається між сайтами:
Сторонні файли cookie традиційно використовуються для відстеження статистики відвідувань та показу реклами. Вони прив’язані до початкового домену, тому ads.com
може відстежувати одного й того ж користувача при переході між сайтами, якщо всі вони мають до нього доступ.
Природно, що деякі люди не хочуть щоб за ними стежили, тому браузери дозволяють їм відключити такі файли cookie.
Крім того, деякі сучасні браузери запроваджують спеціальну політику для таких файлів cookie:
- Safari взагалі не дозволяє сторонні файли cookie.
- У Firefox є “чорний список” сторонніх доменів, з яких він блокує надходження сторонніх файлів cookie.
Якщо ми завантажимо скрипт з стороннього домену, такого як <script src="https://google-analytics.com/analytics.js">
, і цей скрипт використовує document.cookie
щоб встановити файли cookie, то вони не вважатимуться сторонніми.
Якщо скрипт встановлює файл cookie, тоді неважливо звідки завантажений цей скрипт – файл cookie належить домену поточного web-сайту.
Додаток: GDPR
Ця тема взагалі не пов’язана з JavaScript, це просто потрібно пам’ятати, налаштовуючи файли cookie.
У Європі існує законодавство під назвою GDPR, яке встановлює набір правил для вебсайтів щодо поваги до конфіденційності користувачів. Одне з цих правил — вимагати від користувача явного дозволу на відстеження файлів cookie.
Зауважте, що йдеться лише про відстеження/ідентифікацію/авторизацію файлів cookies.
Отже, якщо ми встановлюємо файл cookie, який лише зберігає деяку інформацію, але не відстежує та не ідентифікує користувача, ми можемо це зробити.
Але якщо ми збираємося встановити файл cookie з даними аутентифікації або ідентифікатором відстеження, користувач повинен дозволити це.
Зазвичай, виконання GDPR web-сайтами реалізується в двох варіантах. Ви, напевно, вже бачили їх обидва в мережі інтернет:
-
Якщо вебсайт хоче встановити відстежувальні файли cookie лише для аутентифікованих користувачів.
Для цього в реєстраційній формі має бути прапорець на кшталт “прийняти політику конфіденційності” (яка описує, як використовуються файли cookie), користувач повинен відмітити його, лише тоді вебсайт може встановлювати файли cookie для авторизації.
-
Якщо вебсайт хоче встановити відстежувальні файли cookies для всіх.
Щоб зробити це законно, вебсайт показує спливаюче модальне вікно для нових користувачів і вимагає від них погодитися на файли cookie. Тоді вебсайт може встановлювати їх і дозволити людям бачити контент. Але це може турбувати нових відвідувачів. Ніхто не любить бачити такі спливаючі модальні вікна, які вимагають взаємодії, замість перегляду контенту. Але GDPR вимагає чіткої угоди.
GDPR стосується не лише файлів cookie, а й інших питань, пов’язаних із конфіденційністю, але це виходить за рамки даного розділу.
Підсумки
document.cookie
забезпечує доступ до файлів cookies
- Операції запису змінюють лише вказаний файл cookie.
- Ім’я та значення файлу cookie повинні бути закодовані.
- Один файл cookie не повинен перевищувати 4кБ, 20+ файлів на один сайт (залежно від браузера).
Атрибути:
path=/
, встановлює поточну адресу як типове значення, робить файл cookie видимим лише за вказаною адресою.domain=site.com
, типово файли cookie видимі лише на поточному домені. Якщо домен вказано явно, файли cookie стають видимі на сторінках піддомену.expires
абоmax-age
задають кінцевий термін придатності файлів cookie. Без них файли cookie помирають відразу після закриття вікна браузера.secure
робить файли cookie доступними лише по HTTPS (HTTPS-only).samesite
забороняє браузеру відправляти файли cookie у відповідь на запити які надходять з зовнішнього сайту. Це допомагає запобігти XSRF-атакам.
Додатково:
- Браузери можуть забороняти сторонні файли cookie, наприклад Safari робить це типово. Також ведеться робота, щоб запровадити таке ж і в Chrome.
- Встановлюючи відстежувальний файл cookie для громадян ЄС, GDPR вимагає запитувати дозвіл.