Проблеми безпеки JWT рідко потребують зламу сучасної криптографії. Значно частіше застосунок перевіряє надто мало, довіряє неправильному ключу, зберігає bearer token у небезпечному місці або очікує від самодостатнього токена поведінки серверної сесії. Компактний формат не зменшує відповідальність: навколо нього все одно потрібно спроєктувати повну систему authentication та authorization.

Декодування не є перевіркою

Header і payload JWT можна прочитати без ключа. Якщо застосунок використовує claims до перевірки signature, нападник самостійно створить token із будь-яким user ID або роллю. Усі claims залишаються недовіреними доти, доки verification не завершилася успішно.

Перевірка має відбутися до authorization logic. Система відхиляє malformed token, неправильний підпис, неочікуваний алгоритм, невідомий issuer, неправильний audience, завершений строк і token, який ще не активний.

Токен не повинен обирати security policy

Header називає алгоритм, але verifier порівнює його з allowlist для конкретного issuer і ключа. Поблажливі реалізації історично приймали unsigned tokens або плутали symmetric та asymmetric algorithm. Безпечний код не приймає будь-який алгоритм лише тому, що його просить вхідний token.

Значення kid може допомогти вибрати довірений ключ, але не повинно перетворюватися на unchecked file path, SQL fragment або довільний remote URL. Lookup залишається всередині контрольованого key set.

Bearer token потрібно захищати як credential

Більшість access JWT є bearer tokens: самого володіння достатньо для використання. Валідний підпис не ідентифікує людину, яка подала token. Значення витікає через логи, browser storage, аналітику, error report, URL та скопійовані команди.

Використовуйте TLS, не передавайте token у URL, приховуйте authorization header у логах і робіть access token короткоживучим. У браузері вибір storage має враховувати XSS і CSRF. Жодне місце зберігання не виправляє вразливий застосунок, тому потрібен layered defense.

Довгий строк дії ускладнює revocation

Самодостатній token може залишатися валідним після зміни пароля, звільнення працівника або видалення дозволу. Access token на тиждень дає нападнику тиждень доступу, якщо кожен сервіс не перевіряє revocation list, що частково повертає централізований стан.

Короткі access token обмежують шкоду. Довгоживучий refresh token отримує нові access token через контрольований endpoint і підтримує rotation та відкликання. Ризикові події можуть вимагати invalidation сесій або перевірки server-side token version.

Не кладіть секрети й зайві дані в payload

Signed JWT читається. Паролі, private keys, конфіденційний профіль та внутрішні секрети не належать payload. Навіть звичайні персональні дані варто мінімізувати, бо token поширюється через клієнти й сервіси та може надовго залишитися в діагностичних системах.

Великі token також перевищують proxy або cookie limits і дублюють застарілий стан у кожному запиті. Додавайте claims, які verifier справді потребує, а змінні деталі отримуйте з authoritative source.

Issuer, audience і time claims перевіряються разом

Правильного підпису від відомого ключа недостатньо, якщо token створили для іншого застосунку. Issuer порівнюють з точним очікуваним значенням, а поточний сервіс має належати audience. Expiration і not-before перевіряють із невеликим задокументованим clock skew.

У multi-tenant системі tenant claim має відповідати route й account context. Authorization не повинна просто приймати роль із підписаного payload без перевірки доступу до конкретного об’єкта.

Key rotation потрібно планувати до інциденту

Ключі завершують строк, змінюються й можуть витекти. Issuer публікує current verification keys, надійно їх ідентифікує, захищає private signing key і залишає достатній overlap для старих token. Verifier кешує ключі, але оновлює їх достатньо швидко.

Emergency rotation потребує плану відкликання скомпрометованого ключа та розуміння, які токени ще приймаються. Практична репетиція корисніша за припущення, що витоку не буде.

Authorization залишається відповідальністю застосунку

Token може містити role або scope, але застосунок переводить їх у permission check для конкретного ресурсу. Claim editor не означає автоматичного права редагувати кожен tenant, проєкт чи документ.

Перевірка JWT створює довірений identity context, але не відповідає на всі питання доступу. Object-level authorization має використовувати актуальні доменні правила й окремі тести.

Тестуйте шляхи відмови

Використовуйте підтримувану JWT-бібліотеку й явно задавайте algorithms, keys та claims. Тести повинні довести, що система відхиляє змінений payload, expired token, неправильний audience, неочікуваний issuer, unsigned token і підпис стороннім ключем.

Безпечна реалізація навмисно строга. Вона точно знає, хто може випускати token, де його можна використовувати й скільки часу він діє. Все інше відхиляється, навіть якщо token успішно декодується.

Incident response має враховувати вже видані токени

Після витоку signing key недостатньо просто створити новий. Потрібно припинити довіру до скомпрометованого ключа, оцінити максимальний строк виданих token і вирішити, чи потрібне примусове повторне входження. Важливо знати, які сервіси кешують key set і як швидко вони оновляться.

Після витоку окремого bearer token можна відкликати refresh token, завершити сесію або додати цільовий denylist запис. Реакція залежить від архітектури, тому процедуру потрібно описати до інциденту.

Єдина політика для всіх сервісів

У distributed system небезпечно, коли кожен сервіс по-своєму перевіряє audience, issuer або expiration. Один поблажливий verifier стає слабкою точкою. Спільна бібліотека, централізована конфігурація та contract tests зменшують розбіжності.

Оновлення validation policy повинно мати керований rollout. Нове обов’язкове claim або алгоритм може зламати частину трафіку, якщо issuer і всі consumers не змінюються погоджено.

Моніторинг відмов за причиною показує проблемний сервіс до масового інциденту.

Спільна політика повинна мати власника й процес перегляду.

Це не одноразове налаштування.

Власник відповідає за оновлення.

Зміни проходять review.

Завжди.