Создание JWT: SignJWT

SignJWT — основной механизм создания JWT в библиотеке jose

SignJWT представляет собой высокоуровневый API для формирования и подписания JSON Web Token. Он инкапсулирует процесс подготовки payload, формирования защищённого заголовка и криптографической подписи, обеспечивая строгую совместимость со спецификацией JWT (RFC 7519) и JWS (RFC 7515).

Процесс создания токена через SignJWT всегда состоит из трёх логических частей:

  • Payload — полезные данные токена
  • Protected Header — метаданные алгоритма подписи
  • Криптографическая подпись — результат применения алгоритма к закодированным данным

В библиотеке jose эти этапы объединены в цепочку методов, что позволяет собирать токен декларативно.

Инициализация и базовое использование

Создание JWT начинается с инициализации объекта SignJWT и передачи payload:

import { SignJWT } from 'jose';

const jwt = await new SignJWT({ userId: 123 })
  .setProtectedHeader({ alg: 'HS256' })
  .sign(secret);

Здесь происходит следующее:

  • создаётся payload { userId: 123 }
  • задаётся алгоритм подписи HS256
  • выполняется криптографическая подпись с использованием secret

Формирование payload

Payload представляет собой JSON-объект, который будет закодирован в JWT без шифрования (Base64URL encoding).

new SignJWT({
  userId: 42,
  role: 'admin'
})

Важно учитывать, что payload не является защищённым, поэтому не должен содержать чувствительные данные без дополнительного шифрования (например, JWE).

Protected Header и алгоритмы

Protected header определяет параметры криптографической защиты:

.setProtectedHeader({
  alg: 'HS256',
  typ: 'JWT'
})

Основные алгоритмы, используемые в jose:

  • HS256 / HS384 / HS512 — HMAC с SHA
  • RS256 / RS384 / RS512 — RSA
  • ES256 / ES384 / ES512 — ECDSA
  • EdDSA — Ed25519 / Ed448

Выбор алгоритма напрямую влияет на тип ключа, который требуется для подписи.

Настройка стандартных claims

JWT часто содержит стандартные поля (claims), которые задаются через методы SignJWT.

Временные ограничения

.setIssuedAt()
.setExpirationTime('2h')
  • iat — время создания токена
  • exp — время истечения

Форматы времени могут быть:

  • строка ("2h", "10m", "1d")
  • Unix timestamp

Идентификаторы и контекст

.setSubject('user:42')
.setIssuer('auth.service')
.setAudience('client.app')
.setJwtId('unique-id-123')

Назначение:

  • sub — субъект (обычно пользователь)
  • iss — издатель токена
  • aud — получатель
  • jti — уникальный идентификатор

Полная цепочка построения JWT

SignJWT использует fluent API, где каждый метод возвращает текущий экземпляр:

const token = await new SignJWT({
  userId: 7,
  permissions: ['read', 'write']
})
  .setProtectedHeader({
    alg: 'HS256',
    typ: 'JWT'
  })
  .setIssuer('auth-server')
  .setAudience('web-client')
  .setIssuedAt()
  .setExpirationTime('15m')
  .sign(secretKey);

Каждый вызов добавляет слой метаданных к будущему токену.

Работа с ключами

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

import { createSecretKey } from 'crypto';

const secretKey = createSecretKey(
  Buffer.from('super-secret-key')
);

Используется с HS256 и родственными алгоритмами.

Асимметричные ключи (RSA / ECDSA)

Для RSA:

import { importPKCS8 } from 'jose';

const privateKey = await importPKCS8(rsaPrivateKey, 'RS256');

Для JWK:

import { importJWK } from 'jose';

const key = await importJWK(jwk, 'ES256');

Криптографический процесс подписи

Метод .sign() выполняет несколько внутренних этапов:

  1. Сериализация header
  2. Кодирование payload
  3. Формирование строки header.payload
  4. Применение алгоритма подписи
  5. Кодирование результата в Base64URL
  6. Склейка финального JWT

Результат:

header.payload.signature

Асинхронность SignJWT

Все операции в SignJWT являются асинхронными, поскольку могут включать:

  • работу с WebCrypto API
  • импорт ключей
  • криптографические вычисления

Поэтому использование await является обязательным.

Типичные ошибки при создании JWT

Несовпадение алгоритма и ключа

Использование HS256 с RSA ключом приводит к ошибке подписи.

Отсутствие protected header

JWT без alg считается некорректным:

// ошибка
new SignJWT(payload).sign(key);

Некорректные временные значения

Установка exp в прошлом моментально инвалидирует токен.

Работа с кастомными claims

JWT может содержать произвольные данные:

new SignJWT({
  scope: 'admin',
  features: {
    darkMode: true,
    beta: false
  }
})

Такие поля не интерпретируются библиотекой и передаются как есть.

Использование typ и расширенных заголовков

.setProtectedHeader({
  alg: 'ES256',
  typ: 'JWT',
  kid: 'key-id-1'
})
  • typ — тип токена
  • kid — идентификатор ключа (важно при ротации ключей)

Поведение при сериализации

JWT всегда кодируется в формате Base64URL без padding. jose гарантирует корректное соответствие стандарту, исключая ручные ошибки при кодировании.

Цепочки конфигурации и неизменяемость

После вызова .sign() объект SignJWT становится одноразовым. Повторное использование экземпляра невозможно, что предотвращает случайные утечки состояния между токенами.

Использование с разными сценариями аутентификации

SignJWT применяется в:

  • серверной генерации access token
  • refresh token механизмах
  • межсервисной аутентификации
  • OAuth2 провайдерах

Каждый сценарий отличается набором claims и временем жизни токена.