Hono и Edge-окружения

Edge-окружения накладывают жёсткие ограничения на выполнение JavaScript-кода: нет полноценного Node.js API, отсутствует файловая система, синхронные операции недоступны, а время и память ограничены платформой. В таких условиях криптографические библиотеки должны опираться не на Node.js crypto, а на Web Crypto API, доступный в средах вроде Cloudflare Workers, Vercel Edge Runtime, Deno Deploy и Bun.

Библиотека jose (JavaScript Object Signing and Encryption) изначально ориентирована на работу именно в таких условиях. Она реализует JWS (подпись), JWE (шифрование) и JWT (токены) поверх Web Crypto API без необходимости в нативных модулях Node.js.

Edge-окружения принципиально отличаются от серверного Node.js:

  • отсутствует модуль crypto в привычном виде
  • нельзя использовать бинарные нативные зависимости
  • операции должны быть асинхронными
  • холодный старт имеет значение
  • выполнение происходит в изолированных sandbox-воркерах

В результате любые решения для JWT и криптографии должны:

  • использовать crypto.subtle
  • избегать тяжёлых зависимостей
  • быть tree-shakable
  • работать в ESM-формате

jose соответствует этим требованиям и поэтому стал де-факто стандартом для Edge.

Архитектура jose в контексте Edge

Библиотека построена вокруг модульного подхода:

  • jose/jwt — работа с JWT
  • jose/jws — подпись и верификация
  • jose/jwe — шифрование
  • jose/key — работа с ключами
  • jose/errors — обработка криптографических ошибок

Ключевая особенность — отсутствие зависимости от Node.js API. Всё строится поверх:

  • SubtleCrypto (Web Crypto API)
  • TextEncoder / TextDecoder
  • ArrayBuffer и TypedArray

Использование jose в Hono

Hono — это ультра-лёгкий веб-фреймворк, ориентированный на Edge-окружения. Он идеально сочетается с jose, поскольку сам не требует Node.js runtime и работает поверх Fetch API.

Типичный сценарий — JWT-аутентификация через middleware.

Проверка JWT в Hono middleware

import { Hono } from 'hono'
import { jwtVerify } from 'jose'

const app = new Hono()

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

app.use('/api/*', async (c, next) => {
  const authHeader = c.req.header('Authorization')

  if (!authHeader) {
    return c.json({ error: 'Missing Authorization header' }, 401)
  }

  const token = authHeader.replace('Bearer ', '')

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

    c.set('user', payload)
    await next()
  } catch (err) {
    return c.json({ error: 'Invalid token' }, 401)
  }
})

Этот код работает одинаково в Cloudflare Workers, Vercel Edge и Deno Deploy.

Подпись JWT в Edge-среде

Создание токена через jose выполняется без синхронных операций:

import { SignJWT } from 'jose'

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

const token = await new SignJWT({ userId: 123 })
  .setProtectedHeader({ alg: 'HS256' })
  .setIssuedAt()
  .setExpirationTime('2h')
  .sign(secret)

Здесь важен момент: даже криптографическая операция подписи полностью асинхронна и делегирована Web Crypto API.

Верификация JWT

import { jwtVerify } from 'jose'

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

jwtVerify одновременно:

  • проверяет подпись
  • валидирует алгоритм
  • парсит payload
  • выбрасывает типизированные ошибки при несоответствии

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

Edge-окружения часто используют публично/приватные ключи вместо симметричных секретов.

import { importSPKI, jwtVerify } from 'jose'

const publicKey = await importSPKI(
  `-----BEGIN PUBLIC KEY-----
  ...
  -----END PUBLIC KEY-----`,
  'RS256'
)

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

Использование RSA или ECDSA в Edge важно для распределённых систем, где:

  • подпись выполняется на отдельном auth-сервисе
  • в Edge происходит только проверка

Шифрование JWT (JWE)

В Edge-архитектурах иногда требуется не только подпись, но и шифрование payload.

import { EncryptJWT } from 'jose'

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

const token = await new EncryptJWT({ role: 'admin' })
  .setProtectedHeader({ alg: 'dir', enc: 'A256GCM' })
  .setExpirationTime('1h')
  .encrypt(secret)

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

import { jwtDecrypt } from 'jose'

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

Особенности Web Crypto API

jose полностью зависит от crypto.subtle, что накладывает особенности:

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

Edge-окружения обычно поддерживают:

  • HS256 / HS512
  • RS256 / RS512
  • ES256 (ECDSA P-256)
  • A256GCM (AES-GCM)

Интеграция с Hono Router

Hono позволяет централизовать проверку токенов и доступ к пользователю через контекст:

app.get('/profile', (c) => {
  const user = c.get('user')
  return c.json({ profile: user })
})

Это создаёт единый слой авторизации без привязки к Node.js middleware-моделям.

Производительность в Edge

jose оптимизирован под ограничения Edge:

  • минимальный bundle size за счёт tree-shaking
  • отсутствие лишних polyfill’ов
  • работа напрямую с Web Crypto API
  • lazy-loading отдельных модулей

Однако существуют важные нюансы:

  • RSA операции тяжелее и могут влиять на cold start
  • HS256 быстрее, но менее безопасен при распределённых системах
  • JWE значительно дороже по CPU, чем JWS

Типичные ошибки при работе в Edge

  1. Использование Node.js crypto → в Edge это приводит к runtime error

  2. Передача строк вместо Uint8Array → jose требует явного encoding через TextEncoder

  3. Неверный алгоритм подписи → strict validation в jwtVerify

  4. Попытка синхронной генерации ключей → Edge не поддерживает sync crypto operations

Рекомендованные паттерны архитектуры

В Edge-архитектурах с Hono и jose часто применяются следующие схемы:

  • отдельный auth-service (Node.js или Edge Function)
  • Edge только проверяет подписи JWT
  • минимальный payload токена (userId, roles, exp)
  • использование RS256 для разделения ответственности

Безопасность токенов в Edge

Ключевые принципы:

  • секреты не должны быть захардкожены в Edge-коде
  • предпочтительно использование environment variables платформы
  • короткое время жизни токенов
  • ротация ключей без остановки сервиса

jose поддерживает работу с несколькими ключами одновременно, что упрощает ротацию:

await jwtVerify(token, [key1, key2])

Работа с cookies и Hono

В Edge часто JWT хранится в HttpOnly cookies:

app.use(async (c, next) => {
  const token = c.req.cookie('auth')

  if (!token) return c.json({ error: 'Unauthorized' }, 401)

  const { payload } = await jwtVerify(token, secret)
  c.set('user', payload)

  await next()
})

Это снижает риск XSS-атак по сравнению с localStorage.

Масштабирование Edge JWT-архитектуры

При росте системы важно учитывать:

  • распределённую валидацию токенов
  • независимость Edge-узлов
  • отсутствие shared state
  • возможность оффлайн-верификации через публичные ключи

jose в этом контексте выступает как полностью stateless слой криптографии, что идеально соответствует модели Edge computing.