Перейти к содержимому
Aback Tools Logo

Валидация Конфигурации JWT в TypeScript: Алгоритмы, Claims и Секреты

Валидация конфигурации JWT в TypeScript: принудительное задание алгоритмов в jwt.verify(), проверка claims exp/nbf/iss/aud, ротация ключей через JWKS и отладка токенов браузерными инструментами.

DH
Tutorials & How-Tos12 мин чтения2,750 слов

Большинство багов JWT в TypeScript находятся не в токене, а в конфигурации проверки. Отсутствующее ограничение алгоритма, непроверенный claim издателя или короткоживущий секрет могут незаметно подорвать аутентификацию так, что это проявится только в продакшене. Это руководство проходит по всем аспектам корректности конфигурации JWT в TypeScript: алгоритмы, валидация claims, гигиена секретов, обработка срока действия и браузерные инструменты, ускоряющие отладку.

3Части заголовка JWTзаголовок · payload · подпись
RS256Рекомендуемый алгоритмАсимметричный, безопасный в продакшене
0 КБЗагрузок на серверОтладка JWT в браузере

Что охватывает валидация конфигурации JWT

JSON Web Token (JWT) — компактная, безопасная для URL строка из трёх частей, закодированных в Base64URL и разделённых точками: заголовок, объявляющий алгоритм и тип токена, payload с claims и подпись, связывающая их. Валидация JWT означает подтверждение, что все три части целы и что claims соответствуют требованиям вашего приложения, — а не только что подпись математически корректна.

Два уровня валидации JWT

Криптографическая валидация подтверждает подпись: сервер проверяет, что токен подписан ожидаемым ключом и не был изменён. Валидация конфигурации идёт дальше: она проверяет, что токен выпущен правильным издателем, предназначен именно для этого сервиса, не истёк и несёт ожидаемые пользовательские claims. Большинство уязвимостей JWT возникают из-за неполной валидации конфигурации, а не из-за сломанной криптографии.

  • Алгоритм (`alg`) - должен в точности соответствовать конфигурации сервера; никогда не выводите его из заголовка токена
  • Срок действия (`exp`) - токен не должен пройти отметку истечения, с учётом допуска по часам
  • Не-раньше (`nbf`) - токен нельзя использовать до его самого раннего валидного времени
  • Издатель (`iss`) - токен должен происходить из вашего доверенного сервиса аутентификации
  • Аудитория (`aud`) - токен должен быть предназначен для конкретно этой API или сервиса
  • Пользовательские claims - роль, scope, ID тенанта или любые прикладные поля, от которых зависит ваша логика

Note

Валидация структуры JWT (проверка, что токен — валидная строка из трёх частей Base64URL) — предпосылка для всех остальных проверок. [JWT-декодер и валидатор](/tools/data/validators/jwt-decoder-and-validator) делает это мгновенно в вашем браузере — удобно, чтобы убедиться в корректности токена до написания кода проверки.

Проверки алгоритма и конфигурации ключей

Конфигурация алгоритма — самый критичный для безопасности параметр в проверке JWT. Ошибка в нём открывает класс атак, полностью обходящих аутентификацию. Библиотеки JWT для TypeScript дают инструменты, чтобы enforce это правильно, — но только если вы используете их явно.

HS256 против RS256 - выбор правильного алгоритма

СвойствоHS256 (симметричный)RS256 (асимметричный)
Тип ключаОбщий секрет (один ключ для подписи и проверки)Пара ключей RSA (приватный для подписи, публичный для проверки)
Распределение ключейКаждый проверяющий хранит секретТолько издатель хранит приватный ключ
Безопасность для нескольких сервисов✗ Рискованно - все проверяющие могут подделать токены✓ Проверяющие хранят только публичный ключ
Поддержка OIDC / JWKS✗ Неприменимо✓ Публичные ключи раздаются через JWKS-эндпоинт
Производительность✓ Быстро (HMAC)✗ Медленнее (математика RSA)
Лучше всего дляВнутренние инструменты, API одного сервисаПродакшен-API, распределённые системы, OIDC

Атака подмены алгоритма - и как её предотвратить

Подмена алгоритма происходит, когда сервер читает поле `alg` из заголовка JWT, чтобы решить, как проверять токен, вместо принудительного задания алгоритма собственной конфигурацией. Атакующий меняет заголовок, подставляя `HS256` вместо `RS256`, и подписывает токен публичным ключом сервера, использованным как HMAC-секрет. Неправильно настроенный сервер примет его как валидный. Исправление — одна строка кода, но она должна присутствовать.

typescript
// ✗ Vulnerable - algorithm inferred from token header
jwt.verify(token, publicKey);

// ✓ Correct - algorithm enforced from server configuration
jwt.verify(token, publicKey, { algorithms: ['RS256'] });

Warning

Никогда не опускайте опцию `algorithms` в `jwt.verify()`. Даже если текущая версия библиотеки по умолчанию отвергает алгоритм `none`, явное перечисление разрешённых алгоритмов в коде делает намерение очевидным, переживает обновления библиотеки и полностью устраняет класс уязвимостей подмены алгоритма.

Проверка стойкости ключа для HS256

При использовании HS256 секрет должен быть не короче 256 бит (32 байта), чтобы соответствовать размеру выхода SHA-256. Короткие секреты - менее 32 символов, словарные слова или статические строки вроде `"secret"` или `"development"` - тривиально подбираются перебором инструментами вроде `hashcat` или `jwt_tool`. Генерируйте секреты криптостойким источником случайности: `crypto.randomBytes(32).toString('hex')` в Node.js даёт шестнадцатеричную строку из 64 символов, удовлетворяющую минимальному требованию энтропии.

Валидация стандартных claims JWT в TypeScript

Спецификация JWT определяет набор стандартных зарегистрированных claims, которые должна понимать каждая реализация. Библиотека `jsonwebtoken` автоматически проверяет несколько из них, когда вы передаёте правильные опции, - но ключевое слово «когда передаёте». Без явной конфигурации большинство проверок claims молча пропускается.

Claims exp, nbf и iat

Claim exp (истечение) — метка времени Unix, после которой токен больше не валиден. Библиотека jsonwebtoken проверяет exp по умолчанию в jwt.verify(). Однако рассинхронизация часов между издателем токена и проверяющим может привести к отклонению валидных токенов — частая причина ошибок «токен истёк» в распределённых системах, где серверные часы расходятся на секунды. Передайте clockTolerance, чтобы допустить небольшое окно: clockTolerance = 30 принимает токены до 30 секунд после значения exp.

typescript
interface JwtPayload {
  sub: string;
  iss: string;
  aud: string;
  exp: number;
  iat: number;
  role: 'admin' | 'user';
}

const payload = jwt.verify(token, publicKey, {
  algorithms: ['RS256'],
  issuer: 'https://auth.example.com',
  audience: 'api.example.com',
  clockTolerance: 30,           // seconds of clock skew to tolerate
}) as JwtPayload;

Claims iss и aud

Claim `iss` (издатель) идентифицирует происхождение токена. Claim `aud` (аудитория) идентифицирует предполагаемого получателя. Оба опциональны в спецификации JWT, но критичны на практике. Без валидации `iss` любой сервис, способный выпускать валидные токены вашим ключом подписи, может аутентифицироваться в вашей API. Без валидации `aud` токен, выпущенный для вашего мобильного приложения, может быть переиспользован против админ-API. Передавайте обе опции в `jwt.verify()`, чтобы библиотека принуждала их как жёсткие требования, а не информационные поля.

Tip

Проверьте значения `iss` и `aud` полученного токена, вставив его в [JWT-декодер и валидатор](/tools/data/validators/jwt-decoder-and-validator). Декодированный payload показывает каждый claim в читаемом виде, что упрощает подтверждение соответствия ваших строк издателя и аудитории ожидаемым библиотекой.

Реализация валидации JWT в TypeScript

Полная функция проверки JWT в TypeScript обрабатывает криптографическую валидацию, валидацию claims и классификацию ошибок в одном месте. Вот как её структурировать - с четырьмя шагами, соответствующими схеме HowTo.

1

Убедитесь, что алгоритм соответствует типу ключа

Прежде чем писать код проверки, подтвердите вашу комбинацию алгоритма и ключа. RS256 требует приватный RSA-ключ для подписи и соответствующий публичный ключ для проверки. HS256 требует одинаковый общий секрет с обеих сторон. Их смешивание вызывает исключения времени выполнения, которые трудно диагностировать. Храните публичный ключ или общий секрет в переменных окружения - никогда не хардкодьте их в исходных файлах.

2

Явно валидируйте claims exp и nbf

Всегда задавайте `clockTolerance`, чтобы обрабатывать небольшие расхождения часов между сервисами. Значение 30 секунд — разумный вариант по умолчанию для большинства распределённых систем. Отлаживая `TokenExpiredError` в продакшене, логируйте `payload.exp * 1000` и `Date.now()` вместе - это показывает, на сколько миллисекунд токен прошёл срок, и отличает реальное истечение от проблемы синхронизации часов между сервисом аутентификации и API-сервером.

3

Проверяйте claims iss и aud по ожидаемым значениям

Передайте опции `issuer` и `audience` в `jwt.verify()`, чтобы библиотека отклоняла токены с несовпадающими значениями до выполнения вашего прикладного кода. Если в системе несколько валидных аудиторий (например, и `api.example.com`, и `admin.example.com`), передайте массив: `audience: ['api.example.com', 'admin.example.com']`. Библиотека принимает токен, если claim `aud` совпадает с любым элементом массива.

4

Протестируйте конфигурацию с JWT-декодером

Перед запуском тест-сьюта TypeScript вставьте образец токена из среды разработки или staging в JWT-декодер и валидатор. Визуально подтвердите, что каждый claim - `alg`, `exp`, `iss`, `aud` и ваши пользовательские claims - соответствует опциям вашего `jwt.verify()`. Эта минутная проверка ловит расхождения между содержимым токена и ожиданиями кода до того, как вы потратите время на отладку в тестовой среде.

JWT-декодер и валидатор

Декодируйте JWT-токены и мгновенно проверяйте все claims, поля заголовка и распространённые проблемы безопасности прямо в браузере - без регистрации и загрузки на сервер.

Open tool

Частые ошибки конфигурации JWT

Вот ошибки конфигурации, которые чаще всего встречаются в реализациях JWT на TypeScript. Каждая из них молчит при старте и проявляется только как сбой аутентификации или инцидент безопасности в продакшене.

Использование `jwt.decode()` вместо `jwt.verify()`

Функция `jwt.decode()` извлекает payload без проверки подписи. Она полезна для инспекции уже доверенного токена - например, извлечения ID пользователя из токена, уже проверенного middleware. Она не заменяет `jwt.verify()`. Код, который берёт claims через `jwt.decode()` и на их основе принимает решения авторизации, принимает непроверенные токены. Это полный обход аутентификации.

Игнорирование типа ошибки в блоках catch

Библиотека `jsonwebtoken` выбрасывает три разных типа ошибок: `JsonWebTokenError` (неисправный токен или неверная подпись), `TokenExpiredError` (после claim `exp`) и `NotBeforeError` (до claim `nbf`). Ловить все ошибки как общий `Error` и возвращать `401 Unauthorized` во всех случаях — потеря диагностической информации. Обрабатывайте каждый тип отдельно и возвращайте конкретные сообщения - «токен истёк» против «неверный токен» - чтобы клиенты и системы мониторинга отличали проблемы конфигурации от настоящих попыток атаки.

Отсутствие ротации секретов и пар ключей

Долгоживущие секреты подписи накапливают риск со временем. Секрет, который никогда не ротировался, означает, что при компрометации секрета остаются валидными все выпущенные с ним токены. Реализуйте поле идентификатора ключа (`kid`) в заголовке JWT, чтобы проверяющий мог найти правильный публичный ключ через JWKS-эндпоинт. Этот паттерн позволяет ротацию ключей без аннулирования токенов, подписанных прежним ключом, - каждый токен несёт ссылку на конкретный ключ, которым подписан. Инспектор JWK помогает валидировать вывод JWKS-эндпоинта и обнаруживать материал приватных ключей, который не должен быть публично доступен.

Warning

Никогда не коммитьте секреты JWT или приватные ключи в систему контроля версий. Используйте переменные окружения для всего ключевого материала, загружайте их в рантайме и подтверждайте, что они заданы, прежде чем принимать запросы. Приложение, стартовавшее с неопределённым или пустым секретом, молча примет токены, подписанные пустой строкой.

Принятие алгоритма `none`

Алгоритм `none` создаёт неподписанный JWT - любой payload с валидной структурой проходит проверку. Ранние версии JWT-библиотек принимали `none` по умолчанию. Современные библиотеки его отклоняют, но только когда вы явно указываете разрешённые алгоритмы в опциях проверки. Всегда указывайте `algorithms: ['RS256']` (или ваш конкретный алгоритм), чтобы отклонение `none` было явным и устойчивым к смене версий библиотеки.

Отладка проблем JWT браузерными инструментами

Написать тест для воспроизведения ошибки JWT часто медленнее, чем напрямую осмотреть токен. Браузерные инструменты позволяют за секунды изучить claims, состояние истечения и структуру ключей токена - без локальной среды, без выполнения кода и без отправки чувствительных данных стороннему сервису.

Декодирование и инспекция claims

JWT-декодер и валидатор декодирует заголовок и payload любой строки JWT и представляет все claims в структурированном читаемом виде. Он проверяет распространённые проблемы безопасности - отсутствие `exp`, слабый алгоритм, отсутствие `aud` - и помечает их понятными диагностиками. Вставьте любой токен из среды разработки, staging или продакшена и за десять секунд убедитесь, что claims соответствуют ожиданиям вашего вызова `jwt.verify()`.

Диагностика проблем истечения

Калькулятор обратного отсчёта истечения JWT читает claim `exp` из любого токена и показывает точное оставшееся время жизни или время с момента истечения как в UTC, так и в локальном времени. Когда пользователь сообщает «токен истёк», а ваши логи показывают, что токен ещё должен быть валиден, вставьте его в калькулятор. Сравнение меток времени с точностью до миллисекунд выявит, в чём дело: реальное истечение, расхождение часов между сервисами или значение `exp`, сохранённое в миллисекундах вместо секунд, - на удивление частый баг с множителем 1000.

Инспекция наборов ключей JWKS

При валидации токенов от OIDC-провайдера или любого сервиса, публикующего JWKS-эндпоинт, Инспектор JWK разбирает и валидирует структуру набора ключей. Он проверяет наличие у каждого ключа обязательных полей (`kty`, `use`, `alg`, `kid`), валидирует тип ключа и кривую для EC-ключей и помечает материал приватных ключей, который не должен быть публично доступен. Вставьте JSON из вашего JWKS-эндпоинта прямо в инструмент или укажите URL для загрузки.

Калькулятор обратного отсчёта истечения JWT

Вычислите точный обратный отсчёт истечения JWT по claim exp - увидьте оставшееся время или время с момента истечения в UTC и локальном времени, без кода.

Open tool

Лучшие практики валидации JWT

Корректно настроенная функция проверки JWT необходима, но недостаточна для безопасной аутентификации. Эти практики дополняют картину для продакшен-сервисов на TypeScript.

Используйте типизированный интерфейс payload

Определите TypeScript-интерфейс для вашего JWT-payload и приводите результат проверки к нему. Это даёт безопасность на этапе компиляции для имён claims и типов значений - неверно написанное имя claim (`userId` против `user_id`) станет ошибкой TypeScript вместо тихого `undefined` в рантайме. Храните интерфейс в общем модуле типов, чтобы он был согласован во всех сервисах, проверяющих тот же формат токена.

Короткие сроки жизни токенов с refresh-токенами

Токены доступа должны жить недолго - от 15 минут до 1 часа для большинства API. Долгоживущие токены доступа (дни, недели) расширяют окно, в котором скомпрометированный токен может использоваться. Используйте отдельный механизм refresh-токенов с долгим сроком для поддержания сессии. Refresh-токен ротируется при каждом использовании, а списки отзыва нужны только для refresh-токенов - не для токенов доступа, - пока сроки жизни остаются короткими.

Централизуйте логику проверки

Пишите проверку JWT в одном месте - функция middleware или общая утилита - и используйте её везде. Дублированная логика проверки провоцирует дрейф конфигурации: один эндпоинт проверяет `aud`, другой забывает, и несоответствие эксплуатируется раньше, чем кто-то заметит. Express-middleware, guards NestJS и обработчики маршрутов Next.js имеют чистые паттерны централизации проверок аутентификации. Поместите опции `algorithms`, `issuer` и `audience` в единый объект конфигурации, импортируемый по всему коду.

Настройте проверку один раз, применяйте везде. Единственная опция JWT, которая может отличаться между эндпоинтами, — ожидаемая аудитория.

- Принцип безопасной реализации JWT

Tip

При переходе на нового провайдера аутентификации или обновлении библиотеки JWT используйте [JWT-декодер и валидатор](/tools/data/validators/jwt-decoder-and-validator), чтобы осмотреть токены нового источника до обновления конфигурации проверки. Это подтвердит точные значения `alg`, `iss` и `aud` в новых токенах, чтобы изменения кода соответствовали реальности, а не предположениям.

Key takeaways

  • Всегда явно передавайте `algorithms: ['RS256']` (или ваш конкретный алгоритм) в `jwt.verify()` - никогда не позволяйте библиотеке выводить его из заголовка токена.
  • Валидируйте claims `iss` и `aud` в каждом вызове проверки; их пропуск позволяет токенам из других сервисов аутентифицироваться в вашей API.
  • Используйте `clockTolerance` для расхождений часов между распределёнными сервисами и логируйте метки `exp` вместе с `Date.now()` при отладке ошибок истечения.
  • Никогда не используйте `jwt.decode()` для решений авторизации - он полностью пропускает проверку подписи и принимает любой payload токена.
  • JWT-декодер и валидатор позволяет осмотреть все claims и пометить проблемы конфигурации за секунды, не пиша и не запуская код.
  • RS256 предпочтительнее HS256 для продакшен-API - он устраняет риск распространения общего секрета и поддерживает ротацию ключей на базе JWKS.
  • Держите сроки жизни токенов доступа короткими (15-60 минут) и централизуйте всю логику проверки в единой middleware или утилите, чтобы предотвратить дрейф конфигурации.

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

Use the `jsonwebtoken` library (or `jose` for a modern alternative) and call `jwt.verify(token, secret, { algorithms: ['RS256'], issuer: 'your-issuer', audience: 'your-audience' })`. Always specify the `algorithms` array explicitly - never allow the library to infer it from the token header, as this enables algorithm confusion attacks. Wrap the call in a try/catch and handle `JsonWebTokenError`, `TokenExpiredError`, and `NotBeforeError` separately so you can return informative error responses to API clients.

An algorithm confusion attack occurs when a server accepts the `alg` field from the JWT header to determine how to verify the signature, rather than enforcing the algorithm from its own configuration. An attacker can change `alg` from `RS256` to `HS256` in the header, sign the token with the server's public key as the HMAC secret, and the server will incorrectly validate it as legitimate. The fix: always pass `algorithms: ['RS256']` (or your specific algorithm) explicitly in the verify options.

Call `jwt.verify()` - it throws a `TokenExpiredError` if the `exp` claim is in the past. To inspect expiry without throwing, decode the payload with `jwt.decode(token)` and compare `payload.exp * 1000` to `Date.now()`. For a visual expiry check without writing code, paste your token into the Aback Tools JWT Expiry Countdown Calculator, which shows the exact remaining time or time since expiry in both UTC and local time.

Yes - both are critical. The `iss` (issuer) claim identifies who created the token. Without verifying it, your application will accept tokens issued by any service, including attackers. The `aud` (audience) claim identifies the intended recipient. Without verifying it, a token issued for one of your services can be replayed against another. Pass both as options: `{ issuer: 'https://auth.example.com', audience: 'api.example.com' }`.

HS256 (HMAC-SHA256) uses a single shared secret for both signing and verification. It is simpler to implement but requires every service that verifies tokens to hold the same secret - a security risk in distributed systems. RS256 (RSA-SHA256) uses a private key to sign and a public key to verify. Only the issuing service holds the private key; all consuming services use the public key. RS256 is the recommended algorithm for production APIs where tokens are verified by multiple services or third parties.

After `jwt.verify()` succeeds, cast the result to a typed interface and assert your custom claim values. For example: `const payload = jwt.verify(token, secret) as MyPayload; if (payload.role !== 'admin') throw new Error('Insufficient role')`. Using a TypeScript interface for your JWT payload type gives you compile-time safety on claim names and value types. Validate any claim whose absence or wrong value would represent a security failure - not just standard claims.

The `jose` library is a modern, standards-compliant implementation of JWT, JWS, JWE, JWK, and JWKS that works in Node.js, browsers, Deno, and edge runtimes like Cloudflare Workers. Use `jose` when you need JWKS endpoint support for OIDC, when building for edge or serverless environments, or when you need JWE (encrypted JWT) support. Use `jsonwebtoken` for simple HS256 or RS256 signing and verification in traditional Node.js backends where a shared-secret or static key is sufficient.

Paste the JWT into the Aback Tools JWT Decoder and Validator at abacktools.com/tools/data/validators/jwt-decoder-and-validator. It decodes the header and payload, shows all claims in a readable format, checks for common configuration issues, and flags security problems - all in your browser with no server upload. For expiry checks, the JWT Expiry Countdown Calculator shows exactly how much time remains or how long ago the token expired.

ShareXLinkedIn