Fastify в сочетании с библиотекой Fastify и jose образует высокопроизводительный стек для работы с JWT, JWS, JWE и JWKS, где критически важны скорость, строгая типизация криптографических операций и контроль над жизненным циклом ключей.
Fastify предоставляет минималистичную, но расширяемую архитектуру, в которой ключевыми элементами становятся плагины, хуки и декораторы. Это делает его удобной основой для внедрения криптографической логики без размывания ответственности.
При работе с токенами Fastify чаще всего используется как слой:
Криптографическая часть при этом полностью делегируется jose, которая реализует стандарты JOSE:
Библиотека jose предоставляет строгий и современный API, основанный на Web Crypto API, что делает её предпочтительной для серверных приложений с высокими требованиями к безопасности.
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.
Типичная архитектура строится через плагин 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.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 }
})
При использовании публично-частных ключей jose становится особенно полезной благодаря поддержке PEM и JWK.
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 (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.
Практические рекомендации:
При использовании Fastify и jose ключевыми становятся следующие принципы:
alg)none алгоритма.setProtectedHeader({ alg: 'HS256', typ: 'JWT' })
Помимо подписи, 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, проверяющий токены перед маршрутизацией запроса.
Типовая схема:
Такой подход позволяет:
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 часто встречаются следующие проблемы:
aud и issКорректная модель предполагает:
В сложных системах Fastify может комбинироваться с:
jose при этом остаётся исключительно криптографическим слоем, не вмешиваясь в бизнес-логику безопасности.
Такая декомпозиция позволяет поддерживать высокую скорость обработки запросов при сохранении строгой криптографической модели безопасности