Пошук уроків, статей та іншого контенту
Створимо й перевіримо JWT, розглянемо його структуру, підпис, термін дії та типові помилки використання.
JWT (JSON Web Token) — компактний формат токена, який дає змогу передавати твердження про користувача або запит між частинами системи.
JWT часто використовують для:
автентифікації після входу користувача;
передачі ідентифікатора користувача;
перевірки ролей або дозволів;
обміну даними між сервісами.
JWT має вигляд:
xxxxx.yyyyy.zzzzzТри частини розділені крапками:
Header — заголовок;
Payload — дані токена;
Signature — цифровий підпис.
Важливо: JWT зазвичай
Header — це JSON-об’єкт із метаданими токена.
Наприклад:
{
"alg": "HS256",
"typ": "JWT"
}Основні поля:
alg — алгоритм підпису;
typ — тип токена, зазвичай JWT.
Значення HS256 означає використання HMAC із SHA-256. Для створення та перевірки такого підпису потрібен один і той самий секретний ключ.
Payload містить claims — твердження про токен або його власника.
Приклад:
{
"sub": "user-42",
"role": "reader",
"iat": 1720000000,
"exp": 1720000900
}Поширені стандартні claims:
sub (subject) — ідентифікатор власника токена;
iss (issuer) — сервіс, який створив токен;
aud (audience) — призначений отримувач токена;
exp (expiration time) — час завершення дії;
nbf (not before) — токен не можна використовувати до цього часу;
iat (issued at) — час створення токена;
jti (JWT ID) — унікальний ідентифікатор токена.
Назви та значення claims не захищені від перегляду. Тому в payload не можна зберігати:
паролі;
секретні ключі;
номери банківських карток;
інші конфіденційні дані.
Підпис підтверджує цілісність токена.
Для HS256 він обчислюється приблизно так:
HMAC-SHA256(
base64url(header) + "." + base64url(payload),
secret
)У результаті JWT складається з:
base64url(header).base64url(payload).base64url(signature)Якщо змінити навіть один символ у Header або Payload, підпис перестане відповідати токену, і перевірка завершиться помилкою.
Для роботи використаємо пакет jsonwebtoken.
Встановлення:
npm install jsonwebtokenПриклад створює токен і перевіряє його:
const jwt = require("jsonwebtoken");
const secret = process.env.JWT_SECRET || "local-development-secret";
const token = jwt.sign(
{
sub: "user-42",
role: "reader"
},
secret,
{
algorithm: "HS256",
expiresIn: "15m",
issuer: "fullstack-api",
audience: "fullstack-client"
}
);
console.log("Створений токен:");
console.log(token);
try {
const payload = jwt.verify(token, secret, {
algorithms: ["HS256"],
issuer: "fullstack-api",
audience: "fullstack-client"
});
console.log("Токен дійсний:");
console.log(payload);
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
console.error("Термін дії токена завершився");
} else if (error instanceof jwt.JsonWebTokenError) {
console.error("Некоректний JWT:", error.message);
} else {
console.error("Невідома помилка:", error);
}
}Запустити файл можна так:
JWT_SECRET="strong-secret-value" node app.jsУ Windows значення змінної середовища можна встановити іншим способом, залежно від командної оболонки. Для локального прикладу спрацює і резервне значення в коді, але в реальному застосунку секрет не слід зберігати безпосередньо у файлі.
Функція jwt.sign() приймає:
payload;
секретний ключ або приватний ключ;
параметри токена.
const token = jwt.sign(
{ sub: "user-42" },
secret,
{
algorithm: "HS256",
expiresIn: "15m"
}
);Параметр expiresIn додає claim exp. Він може бути заданий:
expiresIn: "15m"
expiresIn: "2h"
expiresIn: "7d"Також можна використовувати кількість секунд:
expiresIn: 900Для jsonwebtoken значення 900 означає 900 секунд. Claim exp у JWT зберігається як Unix time у секундах.
Не варто самостійно приймати рішення про термін дії лише на основі даних із payload. Перевірка exp має виконуватися через jwt.verify().
Функція jwt.verify():
перевіряє структуру токена;
перевіряє цифровий підпис;
перевіряє термін дії;
перевіряє стандартні claims, якщо для них задано параметри;
повертає перевірений payload.
const payload = jwt.verify(token, secret, {
algorithms: ["HS256"]
});Якщо токен недійсний, функція синхронно викидає помилку. Тому виклик потрібно обробляти через try...catch.
Під час перевірки бажано явно вказувати дозволені алгоритми:
const payload = jwt.verify(token, secret, {
algorithms: ["HS256"]
});Це не дає застосунку приймати токени, підписані несподіваним алгоритмом.
Якщо система використовує асиметричний підпис, наприклад RS256, для створення токена застосовується приватний ключ, а для перевірки — публічний. Алгоритм перевірки все одно потрібно обмежувати відповідним значенням.
Одного правильного підпису іноді недостатньо. Токен міг бути створений іншим сервісом або призначатися іншому клієнту.
Під час створення:
const token = jwt.sign(
{ sub: "user-42" },
secret,
{
algorithm: "HS256",
issuer: "fullstack-api",
audience: "fullstack-client",
expiresIn: "15m"
}
);Під час перевірки:
const payload = jwt.verify(token, secret, {
algorithms: ["HS256"],
issuer: "fullstack-api",
audience: "fullstack-client"
});Токен буде відхилено, якщо:
iss не відповідає fullstack-api;
aud не відповідає fullstack-client;
підпис неправильний;
завершився термін дії;
токен має некоректну структуру.
Найпоширеніші помилки бібліотеки:
TokenExpiredError — термін дії токена завершився;
JsonWebTokenError — токен пошкоджений, підпис неправильний або порушено інше правило перевірки;
NotBeforeError — токен ще не можна використовувати через значення nbf.
Приклад обробки:
try {
const payload = jwt.verify(token, secret, {
algorithms: ["HS256"]
});
console.log(payload);
} catch (error) {
if (error.name === "TokenExpiredError") {
console.log("Потрібно отримати новий токен");
} else if (error.name === "NotBeforeError") {
console.log("Токен ще не активний");
} else if (error.name === "JsonWebTokenError") {
console.log("Токен відхилено");
}
}У відповіді API зазвичай не потрібно розкривати клієнту внутрішні деталі помилки. Наприклад, для всіх недійсних токенів можна повернути загальну відповідь про неавторизований запит.
Метод jwt.decode() лише читає Header або Payload. Він не перевіряє підпис і не гарантує достовірність даних.
const payload = jwt.decode(token);
console.log(payload);Результат decode() не можна використовувати для авторизації:
// Небезпечно: дані не перевірені
const decoded = jwt.decode(token);
if (decoded.role === "admin") {
// Не можна надавати доступ лише на основі decode()
}Для рішень щодо доступу потрібно використовувати результат jwt.verify():
const payload = jwt.verify(token, secret, {
algorithms: ["HS256"]
});
if (payload.role === "admin") {
// Доступ можна розглядати після успішної перевірки токена
}Користувач надсилає облікові дані.
Сервер перевіряє їх.
Сервер створює JWT і повертає його клієнту.
Клієнт надсилає JWT у наступних запитах.
Сервер перевіряє токен до виконання захищеної операції.
Якщо токен недійсний або прострочений, запит відхиляється.
Під час передачі токен зазвичай розміщують у заголовку:
Authorization: Bearer <token>Сам JWT не замінює перевірку дозволів. Після успішної перевірки підпису сервер все одно має перевірити, чи має користувач право виконувати конкретну операцію.
decode() замість verify()decode() не перевіряє підпис. Зловмисник може змінити payload і створити інший непідписаний текст.
Для довіри до даних використовуйте тільки verify().
Поганий варіант:
const secret = "my-secret";Кращий варіант:
const secret = process.env.JWT_SECRET;У production потрібно перевіряти, що змінна середовища справді встановлена, інакше застосунок не повинен запускатися.
Payload можна прочитати без секретного ключа. Підпис підтверджує цілісність, але не приховує вміст.
Токен без exp може залишатися дійсним невизначено довго. Для більшості сценаріїв потрібно встановлювати обмежений термін дії:
expiresIn: "15m"Навіть після перевірки підпису потрібно правильно використовувати sub, role, iss та інші claims. Значення ролі має бути створене довіреним сервером і перевірене разом із підписом токена.
Не слід приймати будь-який алгоритм, указаний у Header. Сервер має заздалегідь визначити допустимі алгоритми через опцію algorithms.
Якщо JWT викрадуть, ним можна буде користуватися до завершення терміну дії. Тому access-токени зазвичай роблять короткоживучими.
JWT складається з Header, Payload і Signature.
Header та Payload кодуються у Base64URL, але не шифруються.
Signature захищає токен від непомітної зміни.
jwt.sign() створює підписаний токен.
jwt.verify() перевіряє підпис, термін дії та налаштовані claims.
Для терміну дії використовують exp або параметр expiresIn.
jwt.decode() не є заміною jwt.verify().
Секрети потрібно зберігати поза кодом.
Алгоритм підпису слід явно обмежувати під час перевірки.
JWT не містить автоматичних дозволів: після перевірки токена сервер має окремо перевірити права користувача.