Сучасні веб‑додатки все частіше спираються на JWT (JSON Web Token) як базовий механізм автентифікації. У середовищі Next.js, яке поєднує серверний рендеринг (SSR) і клієнтський (CSR), правильне управління сесією вимагає продуманої архітектури. У цій статті представлено модель загрози, описано роботу механізму, продемонстровано контраст між вразливим і виправленим кодом та надано чек‑лист мітигації згідно зі стандартами OWASP, CWE і принципом найменших привілеїв.
Модель загрози – що може піти не так?
Основні вектори атак на системи, що базуються на JWT, це:
- крадіжка токена через XSS (впроваджений скрипт у браузері отримує токен з локального сховища або з незахищеного cookie);
- атака CSRF, коли браузер автоматично надсилає cookie з токеном у запиті до API;
- replay‑атака – перехоплений токен використовується повторно до закінчення терміну дії;
- відсутність ротації ключів – у разі витоку можна одноразово заблокувати всі токени.
Цю модель можна уявити як три шари: зберігання токена, його верифікація та управління життєвим циклом. Кожен шар вимагає окремих заходів безпеки.
Як працює механізм JWT у Next.js?
Типовий потік виглядає так:
- Користувач входить у систему, сервер (Node.js) генерує два токени –
accessToken(короткий термін дії) іrefreshToken(довший). - Токени надсилаються у браузер у cookie
HttpOnlyіSecure. - Під час запиту до API браузер автоматично додає
accessToken. Middleware у Next.js перевіряє підпис і дату закінчення терміну дії. - Коли
accessTokenзакінчується, додаток викликає endpoint оновлення, передаючиrefreshToken. Сервер повертає новийaccessTokenі, за потреби, новийrefreshToken.
Ключове – токени не повинні бути доступні в JavaScript (звідси HttpOnly) і кожен endpoint має вимагати верифікації підпису за допомогою актуального ключа.
Приклад: вразливий vs. виправлений код
Нижче наведено два фрагменти – спочатку простий, але небезпечний спосіб зберігання токена у localStorage, а потім рекомендована реалізація з cookie HttpOnly та middleware у Next.js.
// ВРАЗЛИВИЙ – зберігання у localStorage (чутливе до XSS)
function login(credentials) {
fetch('/api/auth/login', {method: 'POST', body: JSON.stringify(credentials)})
.then(r => r.json())
.then(data => {
localStorage.setItem('accessToken', data.accessToken);
localStorage.setItem('refreshToken', data.refreshToken);
});
}
// ВИПРАВЛЕНИЙ – встановлення HttpOnly cookies на боці сервера
// pages/api/auth/login.js
import { sign } from 'jsonwebtoken';
export default async function handler(req, res) {
const { username, password } = req.body;
// ... верифікація credentials ...
const accessToken = sign({ sub: username }, process.env.JWT_ACCESS_SECRET, { expiresIn: '15m' });
const refreshToken = sign({ sub: username }, process.env.JWT_REFRESH_SECRET, { expiresIn: '30d' });
res.setHeader('Set-Cookie', [
`accessToken=${accessToken}; HttpOnly; Secure; SameSite=Strict; Path=/api; Max-Age=900`,
`refreshToken=${refreshToken}; HttpOnly; Secure; SameSite=Strict; Path=/api/auth/refresh; Max-Age=2592000`
]);
res.status(200).json({ message: 'Logged in' });
}
У виправленій версії токени не потрапляють у JavaScript, а прапорці SameSite=Strict обмежують ризик CSRF. Додатково обмежуємо шлях (Path), щоб cookie надсилалися лише до відповідних endpoint‑ів.
Чек‑лісти мітигації – що зробити, щоб відповідати OWASP і CWE
- Зберігання: використовуйте cookie
HttpOnly+Secure; уникайтеlocalStorageіsessionStorageдля токенів. - Обмеження домену та шляху: встановлюйте
DomainіPath, щоб cookie був доступний лише для API. - SameSite: обирайте
StrictабоLaxзалежно від потреб, щоб захиститися від CSRF. - Валідація: перевіряйте підпис, дату закінчення терміну дії та claim‑и (
iss,aud) на боці сервера. - Ротація ключів: регулярно змінюйте секрети JWT; зберігайте попередній ключ короткий час, щоб мати можливість верифікувати існуючі токени.
- Оновлення токенів: використовуйте короткі
accessTokenі довшіrefreshToken; після успішного оновлення анулюйте попередній refresh‑token. - Rate limiting і моніторинг невдалих верифікацій – зменшує ризик brute‑force та token‑replay.
Реалізація вищезазначених пунктів відповідає CWE‑287 (Improper Authentication) та CWE‑352 (Cross‑Site Request Forgery).
Стратегія ротації ключів JWT
Ключі, що використовуються для підпису токенів, мають мати визначений термін дії (наприклад, 30 днів). Процес ротації включає:
- Генерацію нового ключа та збереження його в безпечному сховищі (наприклад, AWS KMS, HashiCorp Vault).
- Додавання нового ключа до списку «прийнятих» у конфігурації застосунку, залишаючи попередній ключ як «fallback».
- Після закінчення перехідного періоду (наприклад, 24 год) видалення старого ключа – всі токени, підписані старим ключем, перестануть прийматися.
У Next.js це можна реалізувати за допомогою допоміжної функції:
// utils/jwt.js
import jwt from 'jsonwebtoken';
const keys = {
current: process.env.JWT_ACCESS_SECRET,
previous: process.env.JWT_ACCESS_SECRET_OLD,
};
export function verify(token) {
try {
return jwt.verify(token, keys.current);
} catch (e) {
// fallback to previous key
return jwt.verify(token, keys.previous);
}
}
Такий підхід задовольняє вимогу OWASP «Key Management» і мінімізує ризик переривання сесії під час ротації.
Типові помилки та компроміси
Even experienced teams make mistakes. Найчастіші – це:
- Встановлення надто довгого часу життя access tokena – збільшує площу атаки у випадку викрадення.
- Відсутність оновлення refresh tokenа – дозволяє необмежене використання викраденого токена.
- Використання одного ключа для access і refresh токенів – ускладнює ротацію і підвищує наслідки витоку.
- Неправильні прапорці SameSite – при
Noneтокени вразливі до CSRF у контексті cross‑site. - Експонування токенів у логах – особливо у середовищах CI/CD.
Компромісні рішення, такі як «sliding expiration» для refresh tokenа, можуть допомогти, але завжди треба оцінювати вплив на безпеку та зручність.
Найкращий захист – це не лише технологія, а й свідоме проектування сесій з самого початку.
Практичний чек‑лист впровадження
- ✅ Використовуйте
HttpOnly,SecureіSameSite=Strictпри встановленні куків з JWT. - ✅ Обмежте
Pathкуків до API‑ендпоінтів. - ✅ Реалізуйте middleware у Next.js, який перевіряє токен при кожному запиті.
- ✅ Створіть ендпоінт оновлення токенів з ротацією refresh tokenа.
- ✅ Впровадьте ротацію ключів кожні 30‑60 днів, залишаючи попередній ключ як fallback.
- ✅ Моніторьте невдалі верифікації та застосовуйте rate limiting.
- ✅ Регулярно переглядайте код на предмет XSS і видаляйте всі небезпечні вставки даних.
Виконуючи цей список, інженерна команда створює міцний фундамент безпеки сесій, відповідний стандартам OWASP Top 10 та CWE‑798 (Use of Hard‑coded Credentials).
Підсумовуючи, безпечні JWT‑токени у Next.js вимагають продуманої стратегії зберігання, регулярної ротації ключів та суворого контролю над життєвим циклом токенів. Реалізуючи описані практики, ви знизите ризик викрадення токенів, атак CSRF і XSS, а також забезпечите відповідність найкращим галузевим стандартам. Якщо потрібна підтримка при аудиті безпеки або впровадженні такої системи у вашій організації, звертайтеся до Coderia.it – ми допоможемо створити рішення, яке відповідатиме найвищим вимогам безпеки та продуктивності.



