JWT токены

JWT (JSON Web Token) используется для передачи проверяемых данных между клиентом и сервером без хранения состояния на сервере. В контексте Fresh — фреймворка для Deno с серверным рендерингом и островной архитектурой — JWT чаще всего применяется для аутентификации и авторизации пользователей в HTTP-запросах.

Fresh по умолчанию не навязывает систему аутентификации. JWT хорошо вписывается в философию минимализма: токен формируется, подписывается, передаётся клиенту и проверяется при каждом запросе без обращения к базе сессий.


Структура JWT

JWT состоит из трёх частей, разделённых точками:

header.payload.signature

Header Содержит метаданные токена:

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload Хранит полезные данные (claims):

{
  "sub": "user_id_123",
  "role": "admin",
  "exp": 1710000000
}

Signature Результат криптографической подписи:

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

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


Claims и их роль

JWT не шифрует данные, а только подписывает. Payload читается любым, кто получил токен.

Зарезервированные claims:

  • iss — издатель токена
  • sub — идентификатор субъекта
  • exp — время истечения
  • iat — время выпуска
  • nbf — токен не действителен до

Пользовательские claims Используются для передачи ролей, прав, флагов доступа. В Fresh это удобно для серверных маршрутов и middleware.


Генерация JWT в Fresh (Deno)

В Deno отсутствует Node.js-специфичный jsonwebtoken, но доступны совместимые библиотеки или стандартный Web Crypto API.

Пример с библиотекой djwt:

import { create, verify, getNumericDate } from "https://deno.land/x/djwt/mod.ts";

const key = "super-secret-key";

const payload = {
  sub: "user_42",
  role: "user",
  exp: getNumericDate(60 * 60),
};

const token = await create(
  { alg: "HS256", typ: "JWT" },
  payload,
  key,
);

Токен формируется асинхронно и может быть возвращён клиенту в ответе API.


Хранение JWT на клиенте

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

HTTP-only cookie

  • Защита от XSS
  • Автоматическая отправка браузером
  • Подходит для SSR в Fresh

Authorization header

Authorization: Bearer <token>
  • Удобно для SPA и fetch-запросов
  • Требует ручного управления

Для Fresh чаще выбирается cookie, так как серверный рендеринг может сразу учитывать данные пользователя.


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

Fresh поддерживает middleware через файл middleware.ts. Проверка токена выполняется до обработки маршрута.

import { MiddlewareHandlerContext } from "$fresh/server.ts";
import { verify } from "https://deno.land/x/djwt/mod.ts";

const key = "super-secret-key";

export async function handler(
  req: Request,
  ctx: MiddlewareHandlerContext,
) {
  const cookie = req.headers.get("cookie");
  const token = cookie?.match(/auth=([^;]+)/)?.[1];

  if (token) {
    try {
      const payload = await verify(token, key, "HS256");
      ctx.state.user = payload;
    } catch {
      // недействительный токен
    }
  }

  return await ctx.next();
}

ctx.state используется для передачи данных пользователя в обработчики маршрутов и страницы.


Использование данных JWT в маршрутах

В обработчике можно проверить наличие пользователя и его права:

export const handler = {
  GET(_req: Request, ctx: HandlerContext) {
    const user = ctx.state.user;

    if (!user) {
      return new Response("Unauthorized", { status: 401 });
    }

    if (user.role !== "admin") {
      return new Response("Forbidden", { status: 403 });
    }

    return new Response("Admin content");
  },
};

Это обеспечивает простой и прозрачный контроль доступа без дополнительной логики сессий.


Истечение срока действия и обновление токенов

JWT всегда должен иметь exp. Отсутствие срока делает токен уязвимым.

Подход с refresh-токеном:

  • access-токен: короткоживущий (5–15 минут)
  • refresh-токен: хранится в HTTP-only cookie
  • при истечении access-токена сервер выдаёт новый

В Fresh refresh-эндпоинт реализуется как обычный API-маршрут.


Безопасность JWT в Fresh

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

  • секретный ключ хранится в Deno.env
  • никогда не хранить чувствительные данные в payload
  • всегда проверять алгоритм подписи
  • использовать HTTPS
  • задавать SameSite, Secure, HttpOnly для cookie

JWT — не замена полноценной системе безопасности, а транспорт данных с проверяемой подписью.


JWT и островная архитектура Fresh

Fresh рендерит HTML на сервере, а интерактивность добавляется через islands. JWT используется только на серверной стороне:

  • middleware извлекает данные
  • страница рендерится уже с учётом пользователя
  • islands получают только необходимые данные через props

Это исключает дублирование логики аутентификации в клиентском коде.


Типичные ошибки

  • использование localStorage для токена при SSR
  • отсутствие exp
  • передача JWT в query-параметрах
  • доверие данным payload без проверки подписи
  • попытка использовать JWT как зашифрованный контейнер

JWT в Fresh работает эффективно при строгом соблюдении этих ограничений и использовании серверной модели обработки запросов.