Fastify и jose

Fastify в сочетании с библиотекой Fastify и jose образует высокопроизводительный стек для работы с JWT, JWS, JWE и JWKS, где критически важны скорость, строгая типизация криптографических операций и контроль над жизненным циклом ключей.

Fastify предоставляет минималистичную, но расширяемую архитектуру, в которой ключевыми элементами становятся плагины, хуки и декораторы. Это делает его удобной основой для внедрения криптографической логики без размывания ответственности.

При работе с токенами Fastify чаще всего используется как слой:

  • аутентификации запросов
  • валидации JWT
  • обогащения контекста пользователя
  • контроля доступа на уровне маршрутов

Криптографическая часть при этом полностью делегируется jose, которая реализует стандарты JOSE:

  • JWS (подпись)
  • JWE (шифрование)
  • JWK (ключи)
  • JWKS (наборы ключей)

Базовые операции JWT через jose

Библиотека jose предоставляет строгий и современный API, основанный на Web Crypto API, что делает её предпочтительной для серверных приложений с высокими требованиями к безопасности.

Подписание токена (JWS)

import { SignJWT } from 'jose'

const secret = new TextEncoder().encode('super-secret-key')

const token = await new SignJWT({ role: 'admin' })
  .setProtectedHeader({ alg: 'HS256' })
  .setSubject('user-123')
  .setIssuedAt()
  .setExpirationTime('2h')
  .sign(secret)

Ключевые элементы:

  • setProtectedHeader — определяет алгоритм
  • setSubject — идентификатор пользователя
  • setExpirationTime — контроль времени жизни
  • sign — финальная криптографическая операция

Проверка токена

import { jwtVerify } from 'jose'

const { payload } = await jwtVerify(token, secret)

console.log(payload.role)

В отличие от классических библиотек JWT, jose строго валидирует алгоритмы, предотвращая атаки типа alg confusion.

Интеграция jose в Fastify через плагин

Типичная архитектура строится через плагин Fastify, который инкапсулирует логику работы с токенами.

import fp from 'fastify-plugin'
import { jwtVerify, SignJWT } from 'jose'

const key = new TextEncoder().encode('super-secret')

export default fp(async function authPlugin(fastify) {
  fastify.decorate('auth', {
    sign: async (payload) => {
      return new SignJWT(payload)
        .setProtectedHeader({ alg: 'HS256' })
        .setIssuedAt()
        .setExpirationTime('1h')
        .sign(key)
    },

    verify: async (token) => {
      return jwtVerify(token, key)
    }
  })
})

После регистрации плагина логика становится доступной во всех обработчиках:

fastify.get('/profile', async (req) => {
  const token = req.headers.authorization?.replace('Bearer ', '')
  const { payload } = await fastify.auth.verify(token)

  return { user: payload }
})

Использование декораторов Fastify

Fastify позволяет расширять объект запроса:

fastify.decorateRequest('user', null)

fastify.addHook('preHandler', async (req) => {
  const token = req.headers.authorization?.replace('Bearer ', '')

  if (!token) return

  const { payload } = await fastify.auth.verify(token)
  req.user = payload
})

Дальнейшая работа упрощается:

fastify.get('/dashboard', async (req) => {
  return { user: req.user }
})

Работа с асимметричными ключами (RS256, ES256)

При использовании публично-частных ключей jose становится особенно полезной благодаря поддержке PEM и JWK.

Генерация и использование RSA ключей

import { generateKeyPair } from 'jose'

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

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

import { SignJWT } from 'jose'

const token = await new SignJWT({ id: 1 })
  .setProtectedHeader({ alg: 'RS256' })
  .setIssuedAt()
  .sign(privateKey)

Проверка публичным ключом

import { jwtVerify } from 'jose'

const { payload } = await jwtVerify(token, publicKey)

JWKS и динамическая ротация ключей

В распределённых системах часто используется JWKS (JSON Web Key Set), особенно при микросервисной архитектуре.

import { createRemoteJWKSet, jwtVerify } from 'jose'

const JWKS = createRemoteJWKSet(
  new URL('https://auth.example.com/.well-known/jwks.json')
)

const { payload } = await jwtVerify(token, JWKS)

Преимущества:

  • автоматическая ротация ключей
  • отсутствие хранения секретов в сервисе
  • централизованное управление безопасностью

Ошибки и обработка исключений

При интеграции Fastify и jose критично централизованно обрабатывать ошибки проверки токенов.

import { JWTExpired, JWSSignatureVerificationFailed } from 'jose'

fastify.setErrorHandler((error, req, reply) => {
  if (error instanceof JWTExpired) {
    reply.code(401).send({ message: 'token expired' })
  }

  if (error instanceof JWSSignatureVerificationFailed) {
    reply.code(401).send({ message: 'invalid token' })
  }

  reply.send(error)
})

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

Fastify изначально оптимизирован под высокую нагрузку, а jose использует Web Crypto API, что снижает нагрузку на Node.js event loop.

Практические рекомендации:

  • избегать повторного декодирования токенов
  • использовать кеширование JWKS
  • не выполнять verify в каждом middleware без необходимости
  • минимизировать payload JWT (не хранить лишние данные)

Безопасные практики работы с JWT

При использовании Fastify и jose ключевыми становятся следующие принципы:

  • всегда фиксировать алгоритм (alg)
  • избегать none алгоритма
  • использовать короткое время жизни токена
  • разделять access и refresh токены
  • хранить секреты вне кода (env/secret manager)
.setProtectedHeader({ alg: 'HS256', typ: 'JWT' })

JWE: шифрование полезной нагрузки

Помимо подписи, jose поддерживает шифрование токенов.

import { EncryptJWT } from 'jose'

const token = await new EncryptJWT({ role: 'admin' })
  .setProtectedHeader({ alg: 'dir', enc: 'A256GCM' })
  .setIssuedAt()
  .encrypt(key)

Расшифровка:

import { jwtDecrypt } from 'jose'

const { payload } = await jwtDecrypt(token, key)

Использование JWE оправдано при хранении чувствительных данных в токене, однако увеличивает накладные расходы.

Интеграция в микросервисную архитектуру

Fastify в распределённой системе часто выступает как edge-service, проверяющий токены перед маршрутизацией запроса.

Типовая схема:

  • API Gateway на Fastify
  • централизованный auth-сервис
  • JWKS endpoint
  • сервисы без знания секретов

Такой подход позволяет:

  • исключить дублирование логики аутентификации
  • централизовать управление ключами
  • упростить масштабирование

Контроль доступа на уровне маршрутов

fastify.get('/admin', {
  preHandler: async (req, reply) => {
    const token = req.headers.authorization?.replace('Bearer ', '')
    const { payload } = await fastify.auth.verify(token)

    if (payload.role !== 'admin') {
      reply.code(403).send()
    }
  }
}, async () => {
  return { secret: true }
})

Типичные архитектурные ошибки

При работе с Fastify и jose часто встречаются следующие проблемы:

  • хранение JWT как единственного источника истины
  • отсутствие ротации ключей
  • использование симметричных ключей в распределённых системах
  • отсутствие проверки aud и iss
  • чрезмерно большой payload токена

Корректная модель предполагает:

  • JWT как транспортный слой
  • сервер как источник авторизации
  • минимальный набор claims

Расширение модели безопасности

В сложных системах Fastify может комбинироваться с:

  • refresh token rotation
  • device binding
  • IP fingerprinting
  • rate limiting на уровне токенов

jose при этом остаётся исключительно криптографическим слоем, не вмешиваясь в бизнес-логику безопасности.

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

  • Fastify обрабатывает HTTP слой
  • jose выполняет криптографические операции
  • JWKS обеспечивает управление ключами
  • hooks Fastify контролируют доступ
  • decorators расширяют контекст запроса

Такая декомпозиция позволяет поддерживать высокую скорость обработки запросов при сохранении строгой криптографической модели безопасности