JWT Refresh Token Rotation: безпечний шаблон для реальних систем

Більшість навчальних посібників JWT зупиняються на «використовуйте маркер оновлення для довготривалих сеансів». Вони рідко пояснюють, що відбувається, коли цей маркер оновлення вкрадено. Статичний токен оновлення, який ніколи не змінюється, надає зловмиснику такий же постійний доступ, як і викрадений файл cookie сеансу. Гірше того, неможливо виявити крадіжку.
Обертання маркерів оновлення вирішує цю проблему, видаючи новий маркер оновлення під час кожного використання. Якщо старий маркер відтворюється повторно, весь сеанс скасовується. У цій статті описано шаблон реалізації, який використовують Auth0, Okta та інші серйозні постачальники ідентифікаційної інформації.
Чому не вдається статичне оновлення маркерів
Статичний маркер оновлення має фіксоване значення протягом усього терміну дії сеансу (часто 30+ днів). Проблеми:
- Крадіжка безшумна — якщо зловмисник вкраде маркер оновлення, він може генерувати нові маркери доступу необмежений час без відома користувача
- Немає виявлення повтору — один і той самий маркер можна використовувати багаторазово з різних місць
- Відкликання є грубим — щоб зробити недійсним один викрадений маркер, часто потрібно анулювати всі сеанси користувача
Виправлення полягає в тому, щоб розглядати кожен маркер оновлення як одноразовий.
Модель обертання
Крок 1: Вхід — Створіть сімейство токенів
Коли користувач автентифікується, згенеруйте family_id (UUID), який групує всі маркери в цьому сеансі:
{ family_id: "f47ac10b-58cc-4372-a567-0e02b2c3d479", user_id: "user_123", refresh_token_hash: sha256(refresh_token), created_at: "2026-03-28T10:00:00Z", expires_at: "2026-04-27T10:00:00Z", used: false
}Зберігати цей запис на сервері. Ніколи не зберігайте маркери оновлення в localStorage — використовуйте httpТільки файли cookie або захищене власне сховище.
Крок 2: Оновлення маркера — обертання
Коли клієнт представляє маркер оновлення:
- Знайдіть хеш маркера в базі даних
- Переконайтеся, що він належить до активної родини та не позначений як
used - Позначити як
used: true - Видайте новий маркер оновлення (нове випадкове значення, те саме
family_id) - Видайте новий маркер доступу
- Зберігати новий хеш маркера оновлення
Крок 3: Виявлення повтору
Якщо маркер оновлення, позначений як used: true, відображається знову, це означає:
- Зловмисник відтворює викрадений маркер, або
- Законний клієнт повторює спробу після збою мережі
Безпечна відповідь: відкликати всі маркери в родині. Це змушує користувача знову увійти. Краще один раз завдати незручностей законному користувачеві, ніж дозволити зловмиснику зберегти доступ.
// Виявлено повтор — ядерний варіант
очікувати db.query( "ВИДАЛИТИ З refresh_tokens WHERE family_id = $1", [familyId]
);Схема бази даних
Мінімальна таблиця маркерів оновлення:
СТВОРИТИ ТАБЛИЦЮ refresh_tokens ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), family_id UUID NOT NULL, user_id UUID NOT NULL REFERENCES users(id), token_hash TEXT NOT NULL UNIQUE, використовується BOOLEAN DEFAULT FALSE, created_at TIMESTAMPTZ DEFAULT зараз(), expires_at TIMESTAMPTZ NOT NULL, replaced_by UUID ПОСИЛАННЯ refresh_tokens(id)
); CREATE INDEX idx_refresh_family ON refresh_tokens(family_id);
CREATE INDEX idx_refresh_user ON refresh_tokens(user_id);Стовпець replaced_by створює ланцюжок, за яким можна стежити для перевірки.
Обробка одночасних запитів
У реальних програмах кілька викликів API можуть ініціювати оновлення одночасно. Якщо Виклик A повертає маркер, а Виклик B все ще зберігає старий, Виклик B запускає виявлення повтору — несподіваний вихід користувача з системи.
Шаблон пільгового періоду
Дозволити попередньому маркеру оновлення залишатися дійсним протягом короткого вікна (5–10 секунд) після ротації:
const GRACE_PERIOD_MS = 10_000; if (token.used) { const elapsed = Date.now() - token.rotated_at; якщо (минуло < GRACE_PERIOD_MS) { // Within grace period — return the already-issued new tokens return getLatestTokensForFamily(token.family_id); } // Outside grace period — replay attack await revokeFamily(token.family_id); throw new UnauthorizedError('token_replayed');
}Моніторинг і сповіщення
Відстежуйте ці сигнали у виробництві:
- Відтворення подій на годину — сплеск вказує на активну атаку або помилку на стороні клієнта
- Сімейні відкликання — високі показники можуть вказувати на крадіжку токена або неправильно налаштований клієнт
- Частота оновлення на користувача — аномально високі частоти вказують на збір токенів
- Географічні аномалії — оновити з іншої країни, ніж вихідний логін
Маркер оновлення проти маркера доступу: короткий довідник
| Property | Access Token | Refresh Token |
|---|---|---|
| Тривалість | 5–15 хвилин | 7–30 днів |
| Формат | JWT (самодостатній) | Непрозорий рядок (рекомендовано) |
| Зберігання | Пам'ять (змінна JS) | httpЛише cookie |
| Надіслано на | API-сервери | Лише сервер авторизації |
| Обертання | Не потрібно | Кожне використання |
Про помилки перевірки вимоги часу, які часто взаємодіють із потоками оновлення, див.
FAQ
Як обробляти одночасні запити на оновлення?
Використовуйте короткий пільговий період (5–10 секунд), коли попередній маркер оновлення все ще приймається. Це обробляє умови змагання, коли кілька викликів API запускають оновлення одночасно. Після пільгового періоду дійсним буде лише найновіший маркер.
Чи мають маркери оновлення бути JWT або непрозорими рядками?
Непрозорі рядки, як правило, безпечніші для маркерів оновлення. JWT несуть корисні дані, які збільшують поверхню атаки, і маркери оновлення не повинні бути автономними, оскільки сервер завжди шукає їх у базі даних.
Як довго мають оновлювати сеанси?
7–30 днів зазвичай. Додатки з високим рівнем безпеки (банківська справа, охорона здоров’я) мають використовувати 1–7 днів. Споживчі програми можуть працювати до 90 днів із ротацією. Завжди поєднуйте довгі сеанси з відбитком пальців пристрою та виявленням аномалій.
Пов’язані інструменти та статті
- JWT Decoder — перевірка маркерів і перевірка часових претензій
- Помилки безпеки JWT — поширені підводні камені JWT, окрім маркерів оновлення
- JWT Time-Claim Bugs — перекіс годинника та проблеми з порядком перевірки
- 2FA Guide — покращити автентифікацію за межами маркерів