jwt-ts
TypeScript-библиотека JWT/JOSE для Node.js подписывает и проверяет JWT/JWS, шифрует JWE и преобразует ключи JWK. Она использует Node crypto и Buffer; это не браузерный SDK и не перенос PHP JWT с идентичными настройками безопасности.
Подключение
Заголовок раздела «Подключение»git clone https://github.com/localzet/jwt-ts.gitcd jwt-tsnpm installnpm run buildnpm packУстановите полученный tarball в приложение. Имя пакета и импорта — @localzet/jwt. Собранный entry point — dist/index.js, декларации типов — dist/index.d.ts. Доступность криптографических алгоритмов зависит от Node.js/OpenSSL вашей среды.
Подпись и проверка JWT
Заголовок раздела «Подпись и проверка JWT»import { randomBytes } from 'node:crypto';import { sign, verify } from '@localzet/jwt';
const key = randomBytes(32);const token = await sign({ sub: 'user-42', iss: 'https://identity.example.test', aud: 'control-panel',}, key, { alg: 'HS256', typ: 'JWT', exp: 300 });
const claims = await verify(token, key, { algorithms: ['HS256'], typ: 'JWT', issuer: 'https://identity.example.test', audience: 'control-panel',});Ключ в примере создаётся для одного запуска. В сервисе он хранится отдельно от исходников и стабильно загружается при запуске; генерация нового ключа на каждый запрос сделает ранее выданные токены непроверяемыми.
exp: 300 в опциях sign означает пять минут от текущего времени, а не абсолютный Unix timestamp. Sign добавляет iat, если не передано iat:false. Payload копируется перед добавлением служебных полей.
Verify проверяет подпись, затем временные claims и ожидаемые issuer/subject/audience/jti. Допустимые алгоритмы задавайте явно: без algorithms библиотека выбирает алгоритм из заголовка токена, включая none. Токен с none не имеет криптографической подписи.
Если нужен обязательный срок действия, отдельно требуйте наличие exp: verifier проверяет его значение только когда поле присутствует. Отзыв токенов, однократность jti и права пользователя библиотека не хранит.
Чтение без проверки
Заголовок раздела «Чтение без проверки»decode(token) возвращает payload; decode(token, { complete: true }) — header, payload и строку signature. Подпись не проверяется. Decode принимает уже два сегмента, тогда как verify требует ровно три. Не используйте декодированные claims как основание для авторизации.
JWS: подпись сообщения
Заголовок раздела «JWS: подпись сообщения»JWS используется, когда нужно подписать сообщение без политики JWT claims. signCompact/verifyCompact работают с тремя сегментами; signGeneral/verifyGeneral — с массивом подписей.
import { signCompact, verifyCompact } from '@localzet/jwt';
const signed = await signCompact({ operation: 'refresh' }, { key, protectedHeader: { alg: 'HS256', typ: 'application/example+json' },});const message = await verifyCompact(signed, key, { expectedAlg: 'HS256', expectedTyp: 'application/example+json',});Обычный compact verifier разбирает payload как JSON. Для произвольных байтов используйте detached-вариант с b64:false, crit:[‘b64’] и передачей точного detachedPayload при проверке. Проверка JWS не проверяет exp, aud или iss как JWT-политику.
В general-форме protected представлен объектом в API этой реализации. Не считайте его автоматически взаимозаменяемым с внешними JOSE JSON-документами, где protected — base64url-строка. Также verifyGeneral не требует непустого массива signatures и при пустом массиве возвращает verified:true: приложение обязано отклонять такие документы. Для обычной аутентификации используйте JWT verify с фиксированной политикой алгоритма.
JWE: шифрование
Заголовок раздела «JWE: шифрование»import { encryptCompact, decryptCompact } from '@localzet/jwt';
const encrypted = await encryptCompact( JSON.stringify({ message: 'Hello' }), { alg: 'dir', key }, {}, 'A256GCM',);const data = await decryptCompact(encrypted, key);Для dir ключ A256GCM должен иметь длину 32 байта. Секретный ключ не передаётся вместе с токеном. Compact JWE содержит пять сегментов; защищённый заголовок используется как дополнительные аутентифицируемые данные.
Расшифровка пытается разобрать plaintext как JSON, иначе возвращает Buffer. При compact-заголовке cty:‘JWT’ возвращается строка вложенного токена: затем нужно отдельно вызвать JWT verify. Успешная расшифровка сама по себе не проверяет JWT claims.
Decrypt не принимает самостоятельный allowlist алгоритмов. Если ключ выбирает resolver, проверяйте разрешённые alg/enc и принадлежность ключа в нём до возврата ключа. Не загружайте jku/x5u из непроверенного заголовка произвольным сетевым запросом.
Выбор и преобразование ключей
Заголовок раздела «Выбор и преобразование ключей»JWKManager преобразует RSA/EC/OKP JWK, KeyObject и PEM. JWKSResolver выбирает первый ключ, совпавший с kid и, если задан, alg. Он не загружает удалённый JWKS, не обновляет кеш и не проверяет назначение key_ops/use.
Храните набор доверенных ключей в приложении; явно отклоняйте неизвестный kid, неоднозначный выбор и несовпадение алгоритма с политикой операции. Само наличие kid не делает ключ доверенным.
Логические ошибки представлены JWTError и его подклассами: InvalidSignatureError, TokenExpiredError, NotBeforeError, JWTClaimValidationError, JoseHeaderError. Разбор JSON, некорректные типы и Node crypto также могут выбросить обычные исключения. Например, HMAC с подписью неверной длины может завершиться ошибкой timingSafeEqual, а не InvalidSignatureError.
Обрабатывайте весь вызов verify/decrypt как границу недоверенного ввода. Не записывайте токены, расшифрованные данные и приватные JWK в публичные логи.
Исходный код · AGPL-3.0