Как декодировать и отлаживать JWT
JSON Web Token — это всего три части, закодированные в Base64URL. Когда вы научитесь их читать, большинство ошибок «invalid token» станут понятны за несколько секунд. В этом руководстве показано, как декодировать JWT, проверить claims, которые чаще всего ломаются, и какие ошибки превращают JWT в уязвимость.
Из чего состоит JWT
JWT выглядит как xxxxx.yyyyy.zzzzz. Три части, разделённые точками, это:
- Header (заголовок) — JSON с алгоритмом подписи (
alg) и типом токена, например{"alg":"HS256","typ":"JWT"}. - Payload (полезная нагрузка) — JSON с claims: о ком токен и когда он действителен.
- Signature (подпись) — хеш первых двух частей, созданный с помощью секрета (HMAC) или закрытого ключа (RSA, ECDSA).
Первые две части только закодированы, но не зашифрованы. Любой, у кого есть токен, может их прочитать, поэтому никогда не кладите пароли и персональные данные в payload JWT.
Декодирование токена
Вставьте токен в JWT Decoder, и он покажет заголовок, payload и все временные метки в виде понятных дат. Всё работает в вашем браузере, поэтому рабочие токены никуда не отправляются. Чтобы декодировать вручную, выполните Base64URL-декодирование каждой части: замените - на +, _ на / и добавьте padding. Или в Node:
const [header, payload] = token.split('.').slice(0, 2)
.map(part => JSON.parse(Buffer.from(part, 'base64url').toString()));
console.log(header, payload);
Claims, которые ломаются чаще всего
| Claim | Значение | Типичная ошибка |
|---|---|---|
exp | Срок действия, Unix-секунды | «jwt expired»: проверьте также часы на сервере и клиенте |
nbf | Не действителен до | Токен использовали на несколько секунд раньше, потому что часы издателя спешат |
iat | Время выпуска | Отклонён как «issued in the future» из-за расхождения часов |
aud | Аудитория, для которой предназначен токен | Токен для одного API отправлен на другой: проверяющая сторона обязана это проверить |
iss | Издатель | Перепутаны издатели dev и production |
Временные метки указаны в секундах, а не в миллисекундах. Если срок истечения выглядит как 55 000 год, кто-то написал Date.now() вместо Math.floor(Date.now() / 1000). Для проверки подойдёт Unix Timestamp Converter. Большинство библиотек допускают небольшую погрешность часов (30–60 секунд), чтобы компенсировать расхождение между серверами.
Почему «invalid signature»?
- Неверный секрет или ключ — самая частая причина. Проверьте переменную окружения на сервере, который выполняет проверку, а не только на том, который подписывает.
- Неверный алгоритм — токен, подписанный RS256, проверяется как HS256, или наоборот.
- Токен был изменён — изменение одного символа в payload ломает подпись, и это как раз задумано.
- Ротация ключей — ключи издателя изменились. При использовании JWKS сопоставьте
kidиз заголовка с опубликованным ключом.
Чтобы проверить токен HS256 вручную, вычислите HMAC-SHA256 от header.payload с секретом в HMAC Generator и сравните результат в кодировке Base64URL с третьей частью.
Ошибки безопасности, которых стоит избегать
- Декодирование — это не проверка. Никогда не доверяйте claims из
jwt.decode(); используйте функцию verify вашей библиотеки с явным списком разрешённых алгоритмов. - Отклоняйте
alg: noneи фиксируйте алгоритм. Никогда не позволяйте токену решать, как его проверять. - Делайте токены короткоживущими (access-токены — минуты) и используйте refresh-токены для длинных сессий. JWT нельзя отозвать до истечения срока без дополнительного состояния на сервере.
- Не храните токены в localStorage, если XSS-уязвимость может их прочитать. Для браузерных приложений безопаснее cookie с флагами HttpOnly, Secure и SameSite.
Нужен тестовый токен с определёнными claims? Создайте его с помощью JWT Encoder.