Пошук уроків, статей та іншого контенту
Розберете cookies, sessions і JWT та зрозумієте, як браузер зберігає й передає дані автентифікації.
Cookie — це невеликий фрагмент даних, який браузер зберігає для певного сайту.
Сервер може надіслати cookie у відповіді:
Set-Cookie: theme=dark; Max-Age=3600; Path=/Після цього браузер автоматично додаватиме cookie до наступних запитів до цього сайту:
Cookie: theme=darkCookies часто використовують для:
збереження налаштувань;
ідентифікації користувача;
зберігання ідентифікатора сесії;
зберігання JWT.
Cookie належить браузеру, але створює та читає її сервер.
HttpOnly — забороняє доступ до cookie з JavaScript через document.cookie.
Secure — браузер передає cookie лише через HTTPS.
SameSite — обмежує передачу cookie під час запитів з інших сайтів.
Path — визначає, для яких шляхів cookie доступна.
Max-Age — час життя cookie у секундах.
Expires — конкретна дата завершення дії cookie.
Для cookie автентифікації зазвичай використовують такі параметри:
HttpOnly; Secure; SameSite=Lax; Path=/Під час локальної розробки через http://localhost параметр Secure часто вимикають. У production-середовищі сайт має працювати через HTTPS.
Session — це стан користувача, який сервер зберігає між запитами.
Наприклад, після входу сервер може створити сесію:
sessionId = abc123На сервері зберігаються дані:
abc123 → {
userId: 42,
email: "anna@example.com"
}А браузер отримує лише ідентифікатор:
Set-Cookie: sessionId=abc123; HttpOnly; Path=/Під час наступного запиту браузер надсилає:
Cookie: sessionId=abc123Сервер знаходить сесію за sessionId і розуміє, хто виконує запит.
Cookie та session — не одне й те саме:
cookie зберігається в браузері;
session зазвичай зберігається на сервері;
cookie може містити ідентифікатор сесії.
Зазвичай у cookie не зберігають усі дані користувача. Там зберігають лише випадковий ідентифікатор, за яким сервер знаходить дані сесії.
JWT, або JSON Web Token, — це підписаний токен, який містить дані у спеціальному форматі.
JWT складається з трьох частин:
header.payload.signatureУ payload можуть бути, наприклад, такі дані:
{
"sub": "42",
"email": "anna@example.com",
"exp": 1893456000
}JWT підписується сервером. Коли клієнт надсилає токен назад, сервер перевіряє:
чи не було змінено токен;
чи правильний підпис;
чи не завершився термін його дії.
Payload JWT зазвичай можна прочитати, якщо отримати сам токен. Підпис захищає від непомітної зміни даних, але не приховує їх.
Тому в JWT не слід зберігати:
пароль;
секретний ключ;
номер банківської картки;
інші конфіденційні дані.
У браузері зберігається ідентифікатор:
sessionId=abc123А всі дані зберігаються на сервері.
Переваги:
сесію легко відкликати на сервері;
дані не потрібно зберігати в браузері;
cookie може бути HttpOnly.
Недолік:
серверу потрібне сховище сесій: база даних, Redis або інше централізоване сховище.
У браузері зберігається сам токен. Сервер перевіряє його підпис і читає дані.
Переваги:
серверу не обов’язково зберігати кожну сесію;
токен містить необхідні дані;
зручно використовувати між різними сервісами.
Недоліки:
відкликати JWT до завершення терміну дії складніше;
більший токен передається з кожним запитом;
потрібно правильно організувати термін дії та оновлення токенів.
JWT також може зберігатися в cookie. Тоді JWT і cookie не є взаємозамінними поняттями:
JWT — це формат токена;
cookie — це спосіб зберігання і передачі даних браузером.
У Next.js cookie можна встановити у відповіді Route Handler.
Створимо демонстраційний endpoint входу:
// app/api/login/route.js
import { NextResponse } from "next/server";
export async function POST(request) {
const body = await request.json();
if (body.email !== "anna@example.com" || body.password !== "secret") {
return NextResponse.json(
{ message: "Неправильний email або пароль" },
{ status: 401 }
);
}
const response = NextResponse.json({
message: "Вхід виконано",
});
response.cookies.set("sessionId", "demo-session-42", {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
path: "/",
maxAge: 60 * 60,
});
return response;
}Після успішного запиту сервер встановить cookie sessionId.
На клієнті можна виконати вхід так:
const response = await fetch("/api/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: "anna@example.com",
password: "secret",
}),
});
const result = await response.json();
console.log(result);JavaScript не зможе прочитати sessionId, тому що cookie має параметр HttpOnly. Це очікувана поведінка.
Браузер сам додасть cookie до наступних запитів на той самий сайт.
У Route Handler можна отримати cookie за допомогою cookies з next/headers:
// app/api/profile/route.js
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
export async function GET() {
const cookieStore = await cookies();
const sessionId = cookieStore.get("sessionId")?.value;
if (!sessionId) {
return NextResponse.json(
{ message: "Потрібно виконати вхід" },
{ status: 401 }
);
}
if (sessionId !== "demo-session-42") {
return NextResponse.json(
{ message: "Недійсна сесія" },
{ status: 401 }
);
}
return NextResponse.json({
user: {
id: 42,
email: "anna@example.com",
},
});
}Тепер запит до /api/profile містить cookie автоматично:
const response = await fetch("/api/profile");
const result = await response.json();
console.log(result);Сервер перевіряє cookie, знаходить користувача та повертає його дані.
У реальному застосунку замість перевірки:
sessionId !== "demo-session-42"сервер шукає ідентифікатор сесії у сховищі.
Щоб виконати вихід, сервер може видалити cookie. На практиці це роблять, встановлюючи порожнє значення та нульовий час життя:
// app/api/logout/route.js
import { NextResponse } from "next/server";
export async function POST() {
const response = NextResponse.json({
message: "Вихід виконано",
});
response.cookies.set("sessionId", "", {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
path: "/",
maxAge: 0,
});
return response;
}Якщо використовується серверна session, під час виходу також потрібно видалити сесію зі сховища. Видалення лише cookie не завжди достатньо: старий ідентифікатор може залишитися дійсним на сервері.
За замовчуванням браузер додає cookies до запитів, якщо:
cookie належить потрібному домену;
шлях запиту відповідає Path;
cookie не завершилася;
виконуються умови Secure і SameSite.
Для запитів у межах одного сайту зазвичай достатньо:
fetch("/api/profile");Якщо запит виконується між різними origin, може знадобитися параметр:
fetch("https://api.example.com/profile", {
credentials: "include",
});У такому випадку сервер також повинен правильно налаштувати CORS. Для початкового Next.js-застосунку, де frontend і API працюють на одному origin, це зазвичай не потрібно.
Є два поширені варіанти.
Сервер встановлює JWT як cookie:
response.cookies.set("accessToken", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
path: "/",
maxAge: 60 * 15,
});Перевага — JavaScript у браузері не може напряму прочитати токен.
Токен можна зберегти так:
localStorage.setItem("accessToken", token);А потім додавати до заголовка:
const token = localStorage.getItem("accessToken");
fetch("/api/profile", {
headers: {
Authorization: `Bearer ${token}`,
},
});У цього підходу є ризик: JavaScript-код, який виконується на сторінці, може прочитати токен. Якщо на сторінці з’явиться шкідливий скрипт, він потенційно зможе отримати токен.
Для cookie автентифікації зазвичай надають перевагу HttpOnly, але це не скасовує необхідність захисту від CSRF та інших атак.
Для cookie, яка використовується для автентифікації:
Використовуйте HttpOnly, щоб JavaScript не читав токен або ідентифікатор сесії.
Використовуйте Secure у production.
Встановлюйте відповідний SameSite, найчастіше Lax.
Не зберігайте паролі та секрети у cookie.
Обмежуйте термін дії cookie.
Під час виходу видаляйте cookie та серверну session.
Не створюйте session ID на основі email, імені або іншого передбачуваного значення.
Не довіряйте даним із cookie без перевірки на сервері.
Cookie можна змінити вручну, тому на сервері потрібно перевіряти її підпис, session ID або JWT.
Класична автентифікація із сесією виглядає так:
Користувач надсилає email і пароль на /api/login.
Сервер перевіряє пароль.
Сервер створює session у сховищі.
Сервер встановлює cookie з ідентифікатором session.
Браузер зберігає cookie.
Браузер автоматично передає cookie у наступних запитах.
Сервер знаходить session і визначає користувача.
Під час виходу сервер видаляє session і cookie.
Потік із JWT подібний, але замість ідентифікатора session сервер створює підписаний JWT.
Cookie не призначена для зберігання паролів. Пароль потрібно передавати лише через HTTPS, перевіряти на сервері та зберігати у вигляді безпечного хешу.
HttpOnlyЯкщо cookie автентифікації доступна через JavaScript, її може прочитати шкідливий скрипт:
document.cookie;Для session ID і JWT зазвичай встановлюють HttpOnly.
Secure на HTTP під час локальної розробкиCookie з Secure не передається через звичайний HTTP. Якщо локальний сервер працює на http://localhost, cookie може не встановлюватися або не надсилатися.
Демонстраційний приклад може зберігати sessions у змінній JavaScript, але це не підходить для production:
дані зникають після перезапуску сервера;
різні екземпляри застосунку не бачать спільні sessions;
серверless-функції не гарантують постійний стан.
Для реального застосунку потрібне надійне зовнішнє сховище.
JWT зазвичай лише підписаний. Його payload не слід вважати таємним.
Cookie, localStorage і заголовки контролює клієнт. Сервер повинен сам перевіряти автентифікацію та права доступу під час кожного захищеного запиту.
Cookie — дані, які браузер зберігає та автоматично передає серверу.
Session — дані про користувача, які сервер зберігає між запитами.
Cookie часто містить лише ідентифікатор session.
JWT — підписаний токен із даними, який сервер може перевірити без пошуку session.
JWT може зберігатися в cookie, тому JWT і cookie — різні поняття.
Для автентифікації важливі HttpOnly, Secure, SameSite, обмежений термін дії та серверна перевірка.
У Next.js cookies можна встановлювати через NextResponse, а читати через cookies з next/headers.