JWT в экосистеме JavaScript чаще всего используется через спецификацию JOSE (JSON Object Signing and Encryption), которая определяет два принципиально разных механизма защиты данных: подписывание (JWS — JSON Web Signature) и шифрование (JWE — JSON Web Encryption). В библиотеке jose оба подхода реализованы через единый набор API, но с разными криптографическими гарантиями и архитектурными последствиями.
Подписанный JWT решает задачу проверки целостности и подлинности данных. Его содержимое не скрывается, а лишь защищается от подделки.
Структура JWS включает три части:
Payload остаётся в открытом виде после base64url-декодирования. Это означает, что любой, кто получает токен, может прочитать его содержимое без ключа.
Основная задача подписи:
import { SignJWT } from 'jose'
const secret = new TextEncoder().encode('super-secret-key')
const token = await new SignJWT({ userId: 123, role: 'admin' })
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('2h')
.sign(secret)
console.log(token)
В этом примере используется симметричный алгоритм HS256, где один и тот же ключ применяется для подписи и проверки.
import { jwtVerify } from 'jose'
const secret = new TextEncoder().encode('super-secret-key')
const { payload } = await jwtVerify(token, secret)
console.log(payload)
Если подпись изменена или токен повреждён, проверка завершится ошибкой.
Шифрованный JWT решает другую задачу — конфиденциальность данных. В отличие от JWS, payload не виден без ключа.
JWE состоит из пяти частей:
В отличие от подписанного токена, содержимое полностью скрыто.
import { EncryptJWT } from 'jose'
const key = new TextEncoder().encode('encryption-key-32-bytes-minimum!!')
const token = await new EncryptJWT({ userId: 123, card: '4111111111111111' })
.setProtectedHeader({ alg: 'dir', enc: 'A256GCM' })
.setIssuedAt()
.setExpirationTime('2h')
.encrypt(key)
console.log(token)
Здесь используется алгоритм A256GCM, обеспечивающий одновременно шифрование и проверку целостности.
import { compactDecrypt } from 'jose'
const key = new TextEncoder().encode('encryption-key-32-bytes-minimum!!')
const { plaintext } = await compactDecrypt(token, key)
const payload = JSON.parse(new TextDecoder().decode(plaintext))
console.log(payload)
После расшифровки данные становятся доступны в исходном виде.
Подписанный JWT и зашифрованный JWT решают разные задачи и часто путаются из-за внешнего сходства формата.
В сложных системах применяется комбинация:
Такой подход обеспечивает одновременно:
Библиотека поддерживает разные криптографические схемы:
Ассиметричные алгоритмы позволяют разделять ключи:
Использование JWS там, где требуется конфиденциальность данных, приводит к утечке информации через payload. JWT часто хранится в браузере или логируется прокси-серверами, поэтому любая незашифрованная информация становится доступной вне контроля приложения.
JWE, в свою очередь, иногда применяют без необходимости, что усложняет систему и увеличивает нагрузку без реальной выгоды, если данные не являются чувствительными.
Библиотека jose построена как модульный набор функций:
SignJWT — создание JWSjwtVerify — проверка JWSEncryptJWT — создание JWEcompactDecrypt — расшифровка JWEКаждая операция строго разделена, что позволяет избегать смешивания логики подписи и шифрования.
Оба типа JWT используют компактный формат:
header.payload.signatureheader.encryptedKey.iv.ciphertext.tagРазличие в количестве сегментов отражает криптографическую сложность операций.
Подписанные JWT чаще применяются в:
Зашифрованные JWT используются в:
Подписанный JWT:
Зашифрованный JWT:
Оба механизма могут использоваться вместе, но решают разные уровни задачи безопасности: доверие и конфиденциальность.