Пошук уроків, статей та іншого контенту
Структура JSON Web Token, потік автентифікації та найпоширеніша помилка щодо його безпеки.
JWT (JSON Web Token) — це компактний формат токена, за допомогою якого одна сторона може передати іншій набір тверджень про користувача або запит.
Найчастіше JWT використовують для:
автентифікації користувачів;
передачі ідентифікатора користувача між сервісами;
авторизації доступу до API;
взаємодії між мікросервісами.
JWT часто називають «токеном автентифікації», але сам по собі він не виконує автентифікацію. Сервер автентифікує користувача під час входу, видає токен, а потім перевіряє цей токен у наступних запитах.
JWT складається з трьох частин, розділених крапками:
header.payload.signatureНаприклад:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiIxMjMiLCJyb2xlIjoidXNlciIsImV4cCI6MTcyMDAwMDAwMH0
.
SflKxwRJSMeKKF2QT4fwpMeJf3bKJ8ZJxJxJxJxJxJxКожна частина кодується у форматі Base64URL.
Важливо: Base64URL — це лише спосіб представлення даних, а не шифрування. Будь-хто, хто отримав JWT, може декодувати його заголовок і payload.
Заголовок описує тип токена та алгоритм підпису:
{
"alg": "HS256",
"typ": "JWT"
}Основні поля:
typ — тип токена, зазвичай JWT;
alg — алгоритм, яким створено цифровий підпис.
Поширені алгоритми:
HS256 — HMAC із симетричним секретом;
RS256 — RSA з приватним і публічним ключами;
ES256 — ECDSA з парою ключів еліптичної криптографії.
Payload містить claims — твердження про токен, користувача або контекст запиту:
{
"sub": "123",
"role": "user",
"iat": 1719990000,
"exp": 1720000000
}Поширені стандартні claims:
sub — суб’єкт токена, наприклад ідентифікатор користувача;
iss — видавець токена;
aud — аудиторія, для якої призначений токен;
iat — час створення токена;
exp — час завершення його дії;
nbf — токен не можна використовувати до цього часу;
jti — унікальний ідентифікатор токена.
Можна додавати власні поля:
{
"sub": "123",
"role": "admin",
"tenantId": "company-42",
"exp": 1720000000
}Однак payload не варто використовувати для зберігання:
паролів;
секретних ключів;
персональних даних, які не повинні бути доступними клієнту;
платіжної або іншої чутливої інформації.
Підпис підтверджує, що токен створив власник ключа і що його вміст не змінили.
Для алгоритму HS256 підпис концептуально створюється так:
HMAC-SHA256(
secret,
base64url(header) + "." + base64url(payload)
)Для RS256 використовується приватний RSA-ключ для створення підпису, а публічний ключ — для його перевірки.
Під час перевірки сервер:
розділяє токен на три частини;
декодує header і payload;
перевіряє дозволений алгоритм;
перевіряє підпис;
перевіряє exp, nbf, iss, aud та інші необхідні claims;
лише після цього довіряє даним із токена.
JWT зазвичай підписаний, але не зашифрований.
Підпис захищає цілісність:
Клієнт не може непомітно змінити
role: "user"наrole: "admin"без знання ключа підпису.
Але підпис не приховує значення payload:
Будь-хто, хто має токен, може прочитати його payload.
Наприклад, payload можна декодувати в JavaScript:
function decodeJwtPart(part) {
// Замінюємо Base64URL на звичайний Base64
const base64 = part.replace(/-/g, "+").replace(/_/g, "/");
// Додаємо відсутні символи вирівнювання
const padded = base64.padEnd(
base64.length + (4 - (base64.length % 4)) % 4,
"="
);
// Декодуємо JSON без перевірки цифрового підпису
return JSON.parse(atob(padded));
}
function decodeJwtPayload(token) {
const [, payload] = token.split(".");
return decodeJwtPart(payload);
}Цей код лише читає payload. Він не доводить, що токен справжній, і не повинен використовуватися для серверної перевірки автентичності.
Якщо потрібно приховати вміст токена, застосовують шифрування, наприклад за допомогою стандарту JWE. Це інша технологія, не те саме, що звичайний підписаний JWT.
Типовий сценарій складається з кількох кроків.
Клієнт надсилає логін і пароль через захищене HTTPS-з’єднання:
POST /login
Content-Type: application/json
{
"email": "user@example.com",
"password": "correct-password"
}Сервер:
знаходить користувача;
порівнює пароль із хешем у базі даних;
створює JWT;
повертає його клієнту або встановлює в cookie.
Паролі не можна зберігати у відкритому вигляді. Для них використовують спеціалізовані алгоритми хешування паролів, наприклад Argon2, bcrypt або scrypt.
Якщо токен передається в заголовку, зазвичай використовують схему Bearer:
GET /api/profile
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...Сервер не повинен просто декодувати payload і вважати його достовірним. Він має перевірити:
структуру токена;
цифровий підпис;
дозволений алгоритм;
термін дії;
видавця;
аудиторію;
необхідні claims;
права доступу до конкретного ресурсу.
Валідний JWT означає, що токен справжній і його можна прийняти в межах заданих правил. Але цього недостатньо для кожної операції.
Наприклад, користувач може бути автентифікованим, але не мати права видаляти чужий документ. Тому після перевірки токена сервер має перевірити дозволи:
Автентифікація: хто це?
Авторизація: що цій особі дозволено?У симетричній схемі для створення та перевірки підпису використовується один секретний ключ.
Переваги:
проста реалізація;
висока швидкість;
зручність для одного сервісу.
Недолік — усі компоненти, які перевіряють токен, повинні знати спільний секрет. Якщо один із них скомпрометований, секрет може бути використаний для створення нових токенів.
В асиметричній схемі:
приватний ключ підписує токен;
публічний ключ перевіряє підпис.
Це зручно в мікросервісній архітектурі. Сервіс авторизації зберігає приватний ключ, а інші сервіси отримують лише публічний ключ.
Вони можуть перевіряти токени, але не можуть створювати нові валідні токени.
У практичних системах часто використовують два типи токенів.
Access token надсилається з API-запитами. Він має короткий термін дії — наприклад, кілька хвилин або десятків хвилин.
Короткий термін зменшує наслідки викрадення токена.
Refresh token використовується для отримання нового access token після завершення його дії. Він живе довше, але застосовується рідше — зазвичай лише на endpoint оновлення сесії.
Спрощений потік:
Клієнт → API: access token
API → Клієнт: 401, токен завершився
Клієнт → Auth-сервіс: refresh token
Auth-сервіс → Клієнт: новий access tokenRefresh token потрібно захищати особливо ретельно. На сервері його часто зберігають у базі даних у вигляді хешу, щоб можна було:
відкликати окрему сесію;
відкликати всі сесії користувача;
виявляти повторне використання токена;
виконувати ротацію refresh token.
Є два поширені підходи.
Токен зберігається в cookie з атрибутами:
HttpOnly — JavaScript не може прочитати cookie;
Secure — cookie надсилається лише через HTTPS;
SameSite — обмежує міжсайтове надсилання cookie.
Перевага — зменшення ризику викрадення токена через JavaScript під час XSS-атаки.
Недолік — браузер автоматично надсилає cookie, тому потрібно враховувати CSRF. Для захисту використовують відповідне налаштування SameSite, CSRF-токени та перевірку походження запиту.
Токен можна зберігати в localStorage або sessionStorage. У такому разі він доступний JavaScript-коду.
Перевага — клієнт явно додає токен у заголовок Authorization, тому класична cookie-атака CSRF не працює так само.
Недолік — шкідливий JavaScript, виконаний у контексті застосунку, може прочитати токен під час XSS-атаки.
Жоден варіант не є автоматично безпечним. Потрібно захищати застосунок від XSS, використовувати HTTPS, скорочувати термін дії access token і правильно налаштовувати політики браузера.
Однією з особливостей JWT є відсутність обов’язкового звернення до бази даних на кожен запит. Сервер може перевірити підпис і термін дії локально.
Але це створює проблему: після видачі токен зазвичай залишається дійсним до завершення exp. Якщо користувач вийшов із системи або його токен викрали, миттєво скасувати такий токен складно.
Можливі стратегії:
використовувати короткоживучі access token;
зберігати стан refresh token на сервері;
відкликати refresh token під час виходу;
підтримувати список відкликаних jti;
використовувати версію сесії або токенів у профілі користувача;
перевіряти стан користувача для критичних операцій.
JWT не означає, що сесія обов’язково має бути повністю stateless. Часто практичним є поєднання самодостатнього access token із серверним контролем refresh-сесій.
Токен, дійсний кілька місяців, стає серйозною проблемою в разі викрадення.
Краще використовувати короткий access token і окремий механізм оновлення сесії.
Payload — це дані від клієнта, доки сервер не перевірив підпис. Не можна визначати роль користувача лише за значенням поля role, прочитаним із токена.
Сервер має явно визначати дозволені алгоритми. Не слід безконтрольно довіряти значенню alg із токена.
Алгоритм, указаний у header, — це інструкція від відправника, а не підстава автоматично змінювати правила перевірки.
Для HMAC потрібен довгий випадковий секрет. Пароль на кшталт secret123 можна підібрати перебором.
Секрети слід зберігати в менеджері секретів або захищених змінних середовища, а не в репозиторії.
Підпис не приховує payload. Навіть якщо користувач не може його змінити, він може його прочитати.
iss та audУ системах із кількома сервісами важливо перевіряти, хто видав токен і для якого сервісу його призначено. Інакше один сервіс може помилково прийняти токен, виданий для іншої аудиторії.
Токени не варто передавати в query-параметрах:
https://example.com/api/data?token=...URL можуть потрапити в історію браузера, журнали сервера, аналітику або заголовок Referer. Для access token краще використовувати Authorization або захищені cookie.
Bearer-токен дає доступ кожному, хто ним володіє. Якщо передати його через незахищене з’єднання, його можна перехопити.
JWT зручний, коли:
кілька сервісів повинні перевіряти токен;
потрібно передавати обмежений набір claims;
важливо зменшити кількість звернень до центрального сервісу сесій;
використовується асиметричний підпис і публічні ключі;
система має зрозумілу стратегію оновлення та відкликання токенів.
Класичні сесії з ідентифікатором у cookie можуть бути кращим вибором, коли:
застосунок монолітний;
потрібне миттєве відкликання сесії;
стан авторизації часто змінюється;
немає потреби передавати claims між багатьма сервісами;
команда хоче централізовано керувати всіма активними сесіями.
JWT не є автоматично сучаснішим або безпечнішим за серверні сесії. Це інший підхід до зберігання та перевірки стану автентифікації.
JWT складається з header, payload і signature. Header описує алгоритм, payload містить claims, а signature захищає токен від непомітної зміни.
Головне, що потрібно запам’ятати:
JWT зазвичай підписує дані, але не шифрує їх;
payload може прочитати кожен, хто отримав токен;
сервер повинен перевіряти підпис, алгоритм, термін дії, видавця й аудиторію;
автентифікація не замінює авторизацію;
access token краще робити короткоживучим;
refresh token потрібно контролювати та відкликати;
вибір між JWT і серверною сесією залежить від архітектури та вимог системи, а не від популярності технології.