Компактная сериализация JWS

Компактная сериализация JSON Web Signature (JWS) представляет подпись в виде одной строки, разделённой точками:

BASE64URL(Protected Header) . BASE64URL(Payload) . BASE64URL(Signature)

Каждая часть строго определена:

  • Protected Header — JSON-объект с параметрами подписи (алгоритм, тип токена и др.)
  • Payload — полезная нагрузка (данные)
  • Signature — криптографическая подпись

Пример:

eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwiYWRtaW4iOnRydWV9.
MEUCIQD...

Используется кодирование Base64URL, отличающееся от стандартного Base64 отсутствием символов +, / и =.


Библиотека jose: ключевые возможности

Библиотека jose реализует стандарты JOSE (JSON Object Signing and Encryption), включая JWS, JWE, JWT. Основные особенности:

  • поддержка современных алгоритмов (ES256, RS256, EdDSA и др.)
  • работа как в Node.js, так и в браузере
  • строгая проверка стандартов RFC

Установка:

npm install jose

Создание JWS (подпись)

Минимальный пример создания JWS с использованием алгоритма ES256:

import { SignJWT, generateKeyPair } from 'jose'

// генерация ключевой пары
const { privateKey } = await generateKeyPair('ES256')

// создание токена
const jwt = await new SignJWT({ user: 'admin' })
  .setProtectedHeader({ alg: 'ES256' })
  .setIssuedAt()
  .setExpirationTime('2h')
  .sign(privateKey)

console.log(jwt)

Разбор этапов:

  • SignJWT — высокоуровневый интерфейс для создания JWS
  • setProtectedHeader — определяет алгоритм подписи
  • setIssuedAt / setExpirationTime — добавляют стандартные поля
  • sign — выполняет криптографическую подпись

Низкоуровневая работа с JWS

Для полного контроля используется класс CompactSign:

import { CompactSign } from 'jose'

const encoder = new TextEncoder()
const payload = encoder.encode(JSON.stringify({ data: 'test' }))

const jws = await new CompactSign(payload)
  .setProtectedHeader({ alg: 'HS256' })
  .sign(secretKey)

Особенности:

  • payload должен быть Uint8Array
  • ручное управление данными и заголовками
  • используется для нестандартных сценариев

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

Для валидации применяется jwtVerify или compactVerify.

Пример с JWT:

import { jwtVerify } from 'jose'

const { payload, protectedHeader } = await jwtVerify(jwt, publicKey)

console.log(payload)
console.log(protectedHeader)

Пример с CompactVerify:

import { compactVerify } from 'jose'

const { payload, protectedHeader } = await compactVerify(jws, key)

const decoded = new TextDecoder().decode(payload)
console.log(JSON.parse(decoded))

Protected Header: ключевые параметры

Наиболее часто используемые поля:

  • alg — алгоритм подписи (обязательный)
  • typ — тип токена (JWT)
  • kid — идентификатор ключа
  • cty — тип содержимого

Пример:

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "key-1"
}

Алгоритмы подписи

Поддерживаемые категории:

Симметричные (HMAC)

  • HS256
  • HS384
  • HS512
import { createSecretKey } from 'crypto'

const key = createSecretKey(Buffer.from('supersecret'))

Асимметричные (RSA, EC)

  • RS256 (RSA)
  • ES256 (Elliptic Curve)
  • EdDSA (Ed25519)
import { generateKeyPair } from 'jose'

const { publicKey, privateKey } = await generateKeyPair('RS256')

Кодирование Base64URL

Пример ручного кодирования:

function base64url(input) {
  return Buffer.from(input)
    .toString('base64')
    .replace(/=/g, '')
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
}

В библиотеке jose это выполняется автоматически.


Валидация токена: важные аспекты

Проверка подписи — только часть процесса. Также необходимо учитывать:

  • срок действия (exp)
  • время выпуска (iat)
  • аудиторию (aud)
  • издателя (iss)

Пример:

await jwtVerify(token, key, {
  issuer: 'https://auth.example.com',
  audience: 'my-app'
})

Работа с ключами (Key Management)

Поддерживаются различные форматы:

  • JWK (JSON Web Key)
  • PEM
  • CryptoKey

Пример импорта JWK:

import { importJWK } from 'jose'

const jwk = {
  kty: 'oct',
  k: 'hJtXIZ2uSN5kbQfbtTNWbg'
}

const key = await importJWK(jwk, 'HS256')

Detached Payload

JWS поддерживает режим, где payload не включён в строку:

BASE64URL(header) .. BASE64URL(signature)

Используется для подписания внешних данных (например, HTTP-тел).


Безопасность

Критические моменты:

  • запрет алгоритма none
  • строгая проверка alg
  • защита от подмены заголовков
  • использование надёжных ключей

Пример ограничения алгоритмов:

await jwtVerify(token, key, {
  algorithms: ['ES256']
})

Производительность и оптимизация

  • предпочтение асимметричных алгоритмов для распределённых систем
  • кеширование ключей
  • минимизация размера payload
  • избегание лишних полей в header

Пример полного цикла

import {
  generateKeyPair,
  SignJWT,
  jwtVerify
} from 'jose'

// генерация ключей
const { publicKey, privateKey } = await generateKeyPair('ES256')

// подпись
const token = await new SignJWT({ role: 'user' })
  .setProtectedHeader({ alg: 'ES256' })
  .setIssuedAt()
  .setExpirationTime('1h')
  .sign(privateKey)

// проверка
const { payload } = await jwtVerify(token, publicKey)

console.log(payload)

Отличия компактной сериализации от JSON-сериализации

Характеристика Compact JWS JSON JWS
Формат строка JSON
Поддержка нескольких подписей нет да
Размер компактный больше
Удобство передачи высокий средний

Компактная сериализация применяется в большинстве случаев, особенно в JWT.


Практическое применение

  • аутентификация (JWT)
  • API авторизация
  • подпись данных
  • межсервисное взаимодействие

Компактный формат удобен для передачи в HTTP-заголовках:

Authorization: Bearer <token>

Распространённые ошибки

  • использование неподдерживаемого алгоритма
  • неправильный формат ключа
  • несоответствие алгоритма и ключа
  • двойное кодирование payload

Диагностика и отладка

Для анализа токена:

const [header, payload, signature] = token.split('.')

console.log(JSON.parse(Buffer.from(header, 'base64url')))
console.log(JSON.parse(Buffer.from(payload, 'base64url')))

Совместимость

Библиотека jose соответствует:

  • RFC 7515 (JWS)
  • RFC 7519 (JWT)
  • RFC 7517 (JWK)

Поддерживается:

  • Node.js (>=16)
  • браузеры (через Web Crypto API)

Архитектурные преимущества

  • строгая типизация API
  • отказ от устаревших алгоритмов
  • единый интерфейс для подписи и проверки
  • модульная структура

Компактная сериализация JWS остаётся базовым форматом для безопасной передачи подписанных данных в современных веб-приложениях.