Skip to content
EN
← Все статьи

JWT-токен: что это, как расшифровать и как проверить

Коротко. JWT — это строка из трёх частей через точку: header.payload.signature. Первые две — обычный base64url, не шифрование: содержимое читает кто угодно. Подпись доказывает, что данные не подменили. Декодировать токен можно локально одной командой, а вот проверить его подлинность — только зная ключ. Секреты в payload класть нельзя.

Схема строения JWT: три блока header, payload и signature, разделённые точками, со стрелками к расшифрованному JSON
JWT состоит из трёх частей. Первые две кодируются base64url и читаются без ключа, третья — подпись.

Что такое JWT и из чего он состоит

JWT (JSON Web Token) — формат передачи набора утверждений о пользователе или клиенте в виде компактной строки. Стандарт описан в RFC 7519, а конкретно подписанный вариант (JWS) — в RFC 7515. Практически всегда, когда говорят «JWT-токен», имеют в виду именно подписанный JWS-вариант из трёх частей.

Строка выглядит так — три блока, разделённые точками:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20iLCJzdWIiOiIxMDI0IiwiYXVkIjoiYXBpLmV4YW1wbGUuY29tIiwiZXhwIjoxNzk4NzYxNjAwLCJpYXQiOjE3OTg3NTgwMDAsImp0aSI6IjlmMmIxYzdhIiwicm9sZSI6ImVkaXRvciJ9.CIcPXbJ7G5DFRj67O7qonOnEZC9tbupi6_xYErENz18

Первая часть — header. Это JSON с типом токена и алгоритмом подписи:

{"alg":"HS256","typ":"JWT"}

Вторая часть — payload (полезная нагрузка). Это JSON с утверждениями — claim'ами:

{
  "iss": "https://auth.example.com",
  "sub": "1024",
  "aud": "api.example.com",
  "exp": 1798761600,
  "iat": 1798758000,
  "jti": "9f2b1c7a",
  "role": "editor"
}

Третья часть — signature. Это подпись первых двух частей: сервер берёт строку header.payload ровно в том виде, в каком она пришла, и считает от неё HMAC или цифровую подпись. Строка, от которой считают подпись, называется signing input — именно поэтому нельзя «пересобрать» токен из декодированного JSON: любой лишний пробел изменит байты и сломает проверку.

base64url — это кодирование, а не шифрование

Обе первые части кодируются base64url (RFC 4648, раздел 5). От обычного base64 он отличается двумя вещами: вместо символов + и / используются - и _, а хвостовые символы выравнивания = отбрасываются. Сделано это ради того, чтобы токен можно было без экранирования положить в URL, заголовок или cookie.

Ключевой вывод: base64url обратим без всякого ключа. Payload не защищён от чтения — он защищён только от изменения. Если внутри токена лежит номер телефона, e-mail или внутренний идентификатор договора, эти данные видит любой, кто получил токен: браузер пользователя, прокси, система логирования, коллега, которому прислали «вот мой запрос, посмотри».

Есть отдельный формат JWE (зашифрованный JWT) — там пять частей вместо трёх, и содержимое действительно нечитаемо без ключа. Но в вебе он встречается редко, и если у вас в токене три части — это обычный подписанный JWS, содержимое открыто.

Стандартные claim'ы

RFC 7519 закрепляет семь зарезервированных полей. Остальные вы добавляете сами (role, tenant_id, scope и т. п.).

ClaimРасшифровкаЧто означаетЧто бывает, если не проверять
ississuerКто выпустил токенПримете токен постороннего эмитента, если он подписан ключом, который у вас в доверенных
subsubjectО ком токен — обычно идентификатор пользователяПривязка запроса не к тому аккаунту
audaudienceКому предназначен токенТокен, выданный соседнему сервису того же провайдера, пройдёт как свой
expexpiration timeДо какого момента действителен. Unix-время в секундах, UTCУкраденный токен работает бессрочно
nbfnot beforeРаньше этого времени токен недействителенЗаранее выпущенный токен начнёт работать преждевременно
iatissued atКогда выпущенПри инциденте нечем отсечь все токены, выпущенные до момента X
jtiJWT IDУникальный идентификатор конкретного токенаНечем отозвать один токен и нечем ловить повторное предъявление

Обратите внимание на exp: это именно Unix-секунды, а не миллисекунды. Значение 1798761600 — это 1 января 2027 года, 00:00 UTC. Частая ошибка на стыке JS и бэкенда — положить туда Date.now(), то есть миллисекунды, и получить токен, который «истекает» через пятьдесят тысяч лет.

Как расшифровать JWT: три способа

Не вставляйте боевой токен в чужие онлайн-декодеры: многие отправляют его на сервер, и что с ним там произойдёт, вы не контролируете. Наш декодер работает целиком в браузере и токен никуда не отправляет — это можно проверить самому: откройте DevTools, вкладку «Сеть», и декодируйте токен, запроса с ним не появится. JWT — предъявительский документ: у кого токен, тот и пользователь, пока не истёк exp. Вставили действующий токен в чужую форму — считайте, что отдали доступ. Для разбора берите токен из тестового контура или из уже отозванной сессии. Если боевой токен всё-таки куда-то попал — завершите сессию и выпустите новый. Хорошая новость: декодирование не требует секрета, поэтому разобрать токен можно локально, никуда его не отправляя.

Отдельно про терминологию. «Расшифровать JWT» — распространённое, но неточное выражение. Расшифровка предполагает ключ; здесь ключ не нужен, потому что шифрования и не было. Правильное слово — декодировать. Это важно не ради педантизма: как только вы поймёте, что payload читается без ключа, станет очевидно, почему в него нельзя класть чувствительные данные.

Способ 1: локально в терминале

Самый безопасный вариант — токен не покидает машину. Проблема одна: base64url нужно превратить в обычный base64 и вернуть выравнивание, иначе base64 -d либо ругнётся, либо молча обрежет последние байты. Рабочий вариант для bash и zsh:

JWT='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20iLCJzdWIiOiIxMDI0IiwiYXVkIjoiYXBpLmV4YW1wbGUuY29tIiwiZXhwIjoxNzk4NzYxNjAwLCJpYXQiOjE3OTg3NTgwMDAsImp0aSI6IjlmMmIxYzdhIiwicm9sZSI6ImVkaXRvciJ9.CIcPXbJ7G5DFRj67O7qonOnEZC9tbupi6_xYErENz18'

for i in 1 2; do
  p=$(printf '%s' "$JWT" | cut -d. -f$i | tr '_-' '/+')
  while [ $(( ${#p} % 4 )) -ne 0 ]; do p="$p="; done
  printf '%s' "$p" | base64 -d; echo
done

Вывод — сначала header, затем payload:

{"alg":"HS256","typ":"JWT"}
{"iss":"https://auth.example.com","sub":"1024","aud":"api.example.com","exp":1798761600,"iat":1798758000,"jti":"9f2b1c7a","role":"editor"}

Если на машине есть Python 3 (а на серверах он есть почти всегда), удобнее один вызов — он сам разбирается с base64url и форматирует JSON:

python3 -c "import base64,json,sys;p=sys.argv[1].split('.')[1];print(json.dumps(json.loads(base64.urlsafe_b64decode(p+'='*(-len(p)%4))),indent=2,ensure_ascii=False))" "$JWT"

Замените индекс [1] на [0], чтобы посмотреть header. Флаг ensure_ascii=False нужен, чтобы кириллица в claim'ах не превратилась в escape-последовательности.

Способ 2: в консоли браузера

Когда токен уже лежит в приложении и нужно быстро понять, что в нём, удобнее декодировать прямо в DevTools. Наивный atob(payload) сломается на двух вещах: символы - и _ и отсутствующее выравнивание. Плюс atob отдаёт «двоичную строку», и кириллица в ней превратится в мусор, если не прогнать её через TextDecoder. Рабочий вариант:

const decodeJwtPart = (part) => {
  const b64 = part.replace(/-/g, '+').replace(/_/g, '/');
  const padded = b64.padEnd(Math.ceil(b64.length / 4) * 4, '=');
  const bytes = Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
  return JSON.parse(new TextDecoder().decode(bytes));
};

const [header, payload] = token.split('.');
console.log(decodeJwtPart(header));
console.log(decodeJwtPart(payload));

Здесь же удобно проверить срок жизни: new Date(payload.exp * 1000). Если получилась дата из 1970 года — в exp положили миллисекунды вместо секунд.

Способ 3: онлайн-декодер

Онлайн-инструмент уместен для тестовых токенов и для быстрой проверки структуры — когда нужно за пять секунд понять, какой алгоритм в header, какие claim'ы есть и когда истекает срок. Наш декодер — /jwt: он разбирает три части, показывает header и payload как читаемый JSON и подсвечивает exp и nbf относительно текущего времени.

Правило простое: тестовый токен — можно, боевой — нет. Если разобрать нужно именно боевой токен, используйте способы 1 и 2 — они не требуют ни секрета, ни сети.

Схема трёх путей декодирования JWT: терминал, консоль браузера и онлайн-декодер, с пометкой что боевой токен не выходит за пределы машины
Декодирование не требует ключа, поэтому боевой токен разбирайте локально, а онлайн-декодер оставьте для тестовых.

Подпись и проверка: чем декодировать отличается от проверить

Это два разных действия, и путаница между ними — источник целого класса уязвимостей.

  • Декодировать — прочитать header и payload. Ключ не нужен. Ничего не доказывает: содержимое мог написать кто угодно.
  • Проверить (verify) — пересчитать подпись по signing input и сравнить с третьей частью, а затем сверить claim'ы. Требует ключа. Только это даёт основание доверять содержимому.

Библиотека, у которой есть метод вида «decode без проверки», почти всегда имеет и метод «verify». Если в коде вызывается первый, а решение об авторизации принимается по его результату — авторизации фактически нет.

Симметричные и асимметричные алгоритмы

АлгоритмТипЧем подписываютЧем проверяютКогда уместен
HS256 / HS384 / HS512Симметричный, HMACОбщий секретТот же секретОдин сервис и выпускает, и проверяет токены
RS256 / RS384 / RS512Асимметричный, RSA PKCS#1 v1.5Приватный ключПубличный ключМного проверяющих сторон, публичный JWKS
PS256 / PS384 / PS512Асимметричный, RSA-PSSПриватный ключПубличный ключСовременная замена RS* при том же типе ключа
ES256 / ES384 / ES512Асимметричный, ECDSAПриватный ключПубличный ключНужна компактная подпись и небольшие ключи
EdDSA (Ed25519)АсимметричныйПриватный ключПубличный ключСовременный вариант там, где его поддерживают обе стороны

Разница практическая. При HS256 всякий, кто может проверить токен, может и выпустить новый: ключ один и тот же. Как только проверяющих сторон становится больше одной — например, десять микросервисов принимают токены одного центра авторизации, — симметричная схема означает, что секрет размазан по десяти сервисам, и компрометация любого из них даёт возможность выпускать токены от имени кого угодно. Асимметричная схема эту проблему снимает: приватный ключ живёт только в центре авторизации, сервисы получают публичный.

Проверка подписи HS256 вручную

Полезно для отладки: сравнить свою подпись с той, что в токене, и понять, тот ли секрет используется.

SIGNING_INPUT="${JWT%.*}"          # всё до последней точки
SECRET='test-secret-not-for-production'

printf '%s' "$SIGNING_INPUT" \
  | openssl dgst -sha256 -mac HMAC -macopt "key:$SECRET" -binary \
  | openssl base64 -A | tr '+/' '-_' | tr -d '='

echo "actual: ${JWT##*.}"

Две строки должны совпасть символ в символ. Если нет — либо секрет другой, либо signing input собран неверно (например, вы пересобрали его из отформатированного JSON вместо исходных байтов).

Проверка подписи RS256

Здесь нужен публичный ключ эмитента — обычно он публикуется по JWKS-эндпоинту провайдера. Подпись сохраняется в бинарный файл и проверяется штатной командой OpenSSL:

printf '%s' "${JWT%.*}" > signing_input.bin

p="${JWT##*.}"
p=$(printf '%s' "$p" | tr '_-' '/+')
while [ $(( ${#p} % 4 )) -ne 0 ]; do p="$p="; done
printf '%s' "$p" | base64 -d > sig.bin

openssl dgst -sha256 -verify pub.pem -signature sig.bin signing_input.bin
# Verified OK

Если изменить в signing input хотя бы один символ, команда вернёт Verification failure — ровно то поведение, ради которого подпись и существует.

Что обязательно проверять на сервере

  1. Подпись — ключом, соответствующим заявленному эмитенту.
  2. Алгоритм — по белому списку. Список задаётся конфигурацией приложения, а не берётся из поля alg самого токена.
  3. exp и nbf — с небольшим допуском на расхождение часов, обычно десятки секунд. Больше минуты допускать не стоит.
  4. iss — строго один из доверенных эмитентов.
  5. aud — строго ваш сервис.
  6. kid, если он есть, — только как ключ поиска в заранее известном справочнике ключей, никогда как путь к файлу или URL.

Классические ошибки и атаки на JWT

Практики безопасного применения собраны в RFC 8725 (JWT Best Current Practices). Ниже — то, что чаще всего встречается в реальном коде.

ОшибкаЧем грозитКак проверитьКак исправить
Принимается alg: noneПолный обход подписи: токен без подписи считается валидным, роль подставляется любаяВ тестовом контуре подменить header на {"alg":"none","typ":"JWT"}, оставить payload, третью часть сделать пустойЖёсткий белый список алгоритмов на стороне верификации; none в нём не должно быть никогда
Подмена RS256 на HS256Публичный ключ, который лежит в открытом доступе, используется как HMAC-секрет — атакующий подписывает токены самПоменять alg на HS256 и подписать токен публичным ключом эмитентаАлгоритм жёстко привязан к типу ключа в конфигурации; проверка не читает alg из токена как источник истины
Не проверяется expУкраденный токен работает бессрочноВыпустить токен с exp в прошлом и отправить запросПроверять exp и nbf; допуск на часы — секунды, не часы
Словарный секрет у HS256Подбор ключа офлайн по одному перехваченному токену, дальше — выпуск любых токеновПрогнать тестовый токен по короткому словарю паролейСекрет не меньше 256 бит из криптостойкого генератора, хранение в секрет-менеджере, плановая ротация
Чувствительные данные в payloadДанные читает каждый, кто получил токен: браузер, прокси, логи, скриншот в чатеПросто декодировать боевой токен и прочитать payloadВ payload — только идентификаторы; всё остальное сервер берёт у себя по sub
Не проверяются iss и audТокен, выданный для другого сервиса того же провайдера, принимается как свойОтправить токен с другим aud и посмотреть, пройдёт лиСверять оба поля со строгим списком допустимых значений
Нет механизма отзываУвольнение, смена пароля, кража устройства не завершают сессию — токен живёт до expСменить пароль и повторить запрос со старым токеномКороткий exp плюс denylist по jti или счётчик версии токена у пользователя
kid подставляется в путь или запросОбращение к постороннему ключу, в тяжёлых случаях — чтение чужих файлов или SQL-инъекцияПодставить в kid значение вида ../../dev/nullkid — только ключ поиска в справочнике; неизвестное значение = отказ
Правило про payload. Всё, что вы кладёте в JWT, вы фактически публикуете. Прежде чем добавить поле, спросите: готов ли я показать это значение в скриншоте, который пользователь пришлёт в поддержку? Если нет — поле в токене не место.
Схема атаки подмены алгоритма: токен с изменённым полем alg проходит проверку, если сервер доверяет значению из самого токена
Атаки на JWT почти всегда сводятся к одному: сервер доверяет полю alg из самого токена вместо своей конфигурации.

Где хранить токен на клиенте

Правильного ответа тут нет — есть осознанный размен между двумя классами атак.

localStorage / sessionStorage. Токен доступен из JavaScript, его удобно подставлять в заголовок Authorization: Bearer. CSRF в этой схеме практически не работает: браузер не подставляет заголовок автоматически, атакующему сайту нечего «прицепить» к запросу. Но любой XSS — включая XSS в стороннем скрипте аналитики или виджете чата — читает хранилище и уносит токен целиком. Дальше токен действует до exp с любого устройства.

httpOnly cookie. Токен недоступен из JavaScript, поэтому XSS не может его вычитать и вынести наружу. Взамен появляется CSRF: браузер сам прикладывает cookie к запросам на ваш домен, и сторонняя страница может инициировать действие от имени пользователя. Лечится атрибутом SameSite=Lax или Strict, отдельным CSRF-токеном для изменяющих запросов и обязательным Secure. Проверить выставленные флаги можно инструментом /cookie.

Честная формулировка размена звучит так: httpOnly cookie не защищает от XSS — она защищает от кражи токена при XSS. Скрипт атакующего всё равно может выполнять запросы от имени пользователя, пока страница открыта, но он не сможет унести токен и пользоваться им завтра со своего сервера. Это существенная разница в масштабе ущерба, поэтому для веб-приложений с сессиями в браузере httpOnly cookie обычно предпочтительнее.

Для мобильных приложений и server-to-server интеграций вопрос не стоит: там нет браузера, токен хранится в защищённом хранилище платформы или в секрет-менеджере, а передаётся заголовком Authorization: Bearer. Что происходит, когда заголовок отсутствует или неверен, разобрано в статье про ошибку 401 Unauthorized.

Срок жизни, refresh-токены и отзыв

Главная неудобная особенность JWT: сервер не хранит состояние, а значит по умолчанию не может ничего отменить. Выпущенный токен действителен до exp — даже если пользователь сменил пароль, лишился прав или уволился. Отсюда два следствия.

Access-токен должен быть коротким. Минуты, а не дни. Тогда окно, в котором украденный токен полезен атакующему, ограничено, и отдельный механизм отзыва нужен реже.

Refresh-токен — это не «то же самое, но подольше». Он предъявляется только на один эндпоинт обновления, хранится строже и, в отличие от access-токена, обычно всё-таки лежит в базе — то есть его можно отозвать. Рабочая схема: короткий access-токен в памяти приложения, refresh-токен в httpOnly cookie, ротация при каждом обновлении.

Ротация с обнаружением повтора. При каждом обновлении выдаётся новый refresh-токен, а старый помечается использованным. Если использованный токен предъявляют повторно — это сигнал, что копия утекла: вся цепочка сессии завершается принудительно. Это дешёвый способ поймать кражу, который не требует ничего, кроме одной таблицы.

Если отзыв «здесь и сейчас» нужен обязательно, вариантов два: denylist отозванных jti с TTL, равным остатку срока жизни токена (в Redis это дёшево), или счётчик версии токенов у пользователя — при смене пароля версия увеличивается, и все токены со старым номером перестают проходить проверку. Оба варианта возвращают в схему состояние на сервере, и это нормально: полностью stateless-аутентификация с мгновенным отзывом не бывает.

Схема жизненного цикла токенов: короткий access-токен, refresh-токен с ротацией и обнаружением повторного использования
Короткий access-токен плюс ротация refresh-токенов с обнаружением повтора — рабочий компромисс между удобством и возможностью отзыва.

JWT или серверные сессии

JWT часто выбирают по инерции, потому что «так делают в микросервисах». Для монолита с одной базой это обычно проигрыш: классическая сессия проще, отзывается мгновенно и не тащит килобайт в каждый запрос.

КритерийJWT (stateless)Серверная сессия
Хранение состоянияНичего в базе — всё в токенеЗапись в БД или Redis плюс cookie с идентификатором
Мгновенный отзывСложно: нужен denylist, то есть состояние возвращаетсяТривиально: удалить запись
Много независимых сервисовПлюс: проверка по публичному ключу без похода в общее хранилищеНужен общий стор сессий, доступный всем сервисам
Объём на каждый запросСотни байт, при богатом payload — килобайтыДесятки байт в cookie
Смена прав пользователяСтарый токен несёт старую роль до expНовые права действуют со следующего запроса
Сложность реализацииВыше: алгоритмы, ключи, ротация, refreshНиже: почти всегда встроено во фреймворк
Когда уместноМежсервисное взаимодействие, API для сторонних клиентов, SSO и OIDCМонолит, классическое веб-приложение с одной базой

Практический ориентир: если у вас один бэкенд и одна база — берите сессии. Если токен должен проверяться несколькими независимыми сервисами или сторонними клиентами и поход в общее хранилище на каждый запрос неприемлем — берите JWT и сразу закладывайте короткий exp и механизм отзыва.

Как проверить свой токен

Порядок разбора, когда «токен не работает» или непонятно, что в нём:

  1. Разберите структуру. Возьмите тестовый токен и откройте /jwt. Убедитесь, что частей ровно три, header парсится, а payload читается как JSON. Если частей пять — это JWE, и его содержимое без ключа не прочитать.
  2. Посмотрите alg. none в боевом токене — красный флаг. Смена алгоритма между окружениями — вторая по частоте причина отказов проверки.
  3. Сверьте exp и nbf с текущим временем. Токен, выглядящий «свежим», часто оказывается просроченным на несколько минут из-за расхождения часов между сервисами.
  4. Проверьте iss и aud. Токен, выданный для стенда, не подойдёт продакшену — и это правильное поведение, а не баг.
  5. Пересчитайте подпись локально командами выше, если есть доступ к ключу.
  6. Проверьте транспорт. Токен, отданный по HTTP, перехватывается по дороге. Убедитесь, что HTTPS настроен и работает без предупреждений: /ssl. Заголовки ответа приложения смотрите через /http-headers, флаги cookie — через /cookie.
  7. Оцените обвязку. Сканер /security покажет заголовки безопасности, включая разбор Content-Security-Policy, — это то, что снижает шанс на XSS, а значит и на кражу токена из браузера.

Сам декодер, помимо разбора, отмечает отсутствующие claim'ы — например, что без iss эмитент не верифицируется — и объясняет, чем каждый пропуск грозит. Смежные материалы: безопасность API, проверка безопасности сайта и разбор HTTP-заголовков.

Частые вопросы

Можно ли расшифровать JWT без ключа?

Да, если под «расшифровать» понимать «прочитать содержимое». Header и payload — это base64url, а не шифр: они декодируются одной командой без всякого секрета. Ключ нужен только для того, чтобы проверить подпись, то есть убедиться, что содержимое не подменили. Исключение — формат JWE (пять частей вместо трёх): он действительно зашифрован, и без ключа payload не прочитать.

Как получить JWT-токен для работы с API?

Стандартный путь: отправить логин и пароль либо client_id и client_secret на эндпоинт авторизации провайдера и получить в ответ JSON с полями вида access_token, expires_in и часто refresh_token. Дальше токен подставляется в заголовок Authorization: Bearer <token> каждого запроса. Точный адрес эндпоинта и формат тела запроса всегда смотрите в документации конкретного API — единого для всех варианта не существует.

Как работать с JWT-токеном в 1С?

Принцип тот же, что и в любом другом клиенте, конфигуратор здесь ничего не меняет. Нужно выполнить HTTP-запрос к эндпоинту авторизации внешнего сервиса, разобрать JSON-ответ и достать из него access-токен, а затем подставлять его в заголовок Authorization со значением Bearer <token> у всех последующих запросов. Ключевой практический момент: токен имеет срок жизни, поэтому его стоит кэшировать вместе с моментом истечения и обновлять заранее — за минуту-две до exp, а не по факту получения ошибки 401. Второй момент: секрет для получения токена не должен лежать в коде обработки — храните его в защищённом реквизите или в настройках подключения.

Почему сервер отвечает 401, хотя токен на вид правильный?

Самые частые причины по убыванию: истёк exp; не совпадает aud (токен выдан для другого сервиса); расходятся часы между эмитентом и потребителем, и nbf ещё не наступил; токен подписан ключом другого окружения; заголовок передан без префикса Bearer или с лишним пробелом. Разбор кода 401 и схем авторизации — в отдельной статье.

Какой длины должен быть секрет для HS256?

Не короче размера выхода хеш-функции — для HS256 это 256 бит, то есть 32 байта случайных данных из криптостойкого генератора. Осмысленная фраза, даже длинная, не годится: стойкость определяется энтропией, а не количеством символов. Секрет из словаря подбирается офлайн по одному перехваченному токену, после чего атакующий выпускает любые токены сам.

Нужно ли шифровать JWT?

В большинстве случаев нет — вместо этого не кладите в payload то, что не должно быть публичным. Формат JWE существует и решает задачу, но добавляет управление ещё одним ключом и заметно усложняет отладку. Если данные действительно секретны, обычно проще и надёжнее оставить в токене только идентификатор, а сами данные держать на сервере.

Что делать, если боевой токен попал в чужой декодер?

Считать его скомпрометированным. Завершите соответствующую сессию, отзовите refresh-токен, при наличии denylist добавьте jti. Если механизма отзыва нет — смените пароль пользователя или увеличьте счётчик версии токенов, если такой предусмотрен. Дополнительно проверьте, не попал ли токен в логи прокси или системы мониторинга: типовая утечка — токен в query-параметре, который пишется в access-лог целиком.

Чеклист

  • Помню, что payload — это base64url, а не шифр: чувствительных данных внутри нет.
  • Боевые токены декодирую только локально — в терминале или в консоли браузера; онлайн-декодер использую для тестовых.
  • Верификация принимает алгоритм строго по белому списку из конфигурации; none невозможен.
  • Алгоритм жёстко связан с типом ключа — подмена RS256 на HS256 не проходит.
  • Проверяю exp, nbf, iss и aud, допуск на расхождение часов — секунды.
  • kid используется только как ключ поиска в известном справочнике.
  • Секрет HS256 — не менее 256 бит из криптостойкого генератора, лежит в секрет-менеджере, ротируется.
  • Access-токен короткий; refresh-токен ротируется, повторное использование обнаруживается.
  • Есть ответ на вопрос «как отозвать токен прямо сейчас»: denylist по jti или версия токенов у пользователя.
  • Выбор хранилища на клиенте сделан осознанно: localStorage — риск XSS, httpOnly cookie — нужна защита от CSRF и флаг Secure.
  • Токен никогда не передаётся в query-параметре — только в заголовке или cookie.
  • Транспорт только HTTPS; сертификат и заголовки безопасности проверены.
  • Для монолита с одной базой честно рассмотрел вариант обычных серверных сессий.

Проверьте ваш сайт прямо сейчас

Проверить безопасность сайта →
Другие статьи: Безопасность
Безопасность
Правила WAF: написание эффективных политик веб-файрвола
16.03.2026 · 701 просм.
Безопасность
Как проверить сайт на вирусы: 4 уровня проверки и план лечения
01.04.2026 · 502 просм.
Безопасность
Заголовки безопасности: CSP, HSTS, X-Frame-Options и другие
10.03.2025 · 411 просм.
Безопасность
Реестр блокировок РКН в цифрах: анализ 131 000 заблокированных доменов (2026)
26.06.2026 · 395 просм.