Защита маршрутов

Fresh — это современный фреймворк для разработки веб-приложений на платформе Deno, ориентированный на высокую производительность и простоту работы с серверным рендерингом. Одной из ключевых задач при построении приложений является защита маршрутов и контроль доступа к определённым страницам или API.

Middleware и обработка маршрутов

В Fresh маршруты определяются через файловую структуру в папке routes. Каждый файл или папка с файлом index.ts автоматически становится доступным как маршрут. Для защиты маршрутов используется middleware — промежуточный слой, который проверяет состояние запроса перед выполнением основного обработчика.

Пример базового middleware для проверки аутентификации:

import type { MiddlewareHandlerContext } from "$fresh/server.ts";

export async function authMiddleware(
  req: Request,
  ctx: MiddlewareHandlerContext
) {
  const token = req.headers.get("Authorization");
  if (!token || token !== "Bearer valid-token") {
    return new Response("Unauthorized", { status: 401 });
  }
  return await ctx.next();
}

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

  • ctx.next() передаёт управление следующему обработчику маршрута, если проверка прошла успешно.
  • Возврат Response с ошибкой блокирует дальнейшую обработку.

Применение middleware к маршрутам

Middleware в Fresh можно подключать глобально или к конкретным маршрутам через route.ts:

import { authMiddleware } from "../middlewares/auth.ts";
import handler from "./handler.ts";

export const handlerWithAuth = [
  authMiddleware,
  handler
];

Такой подход позволяет создавать цепочки проверки: сначала проверка токена, затем логирование, и только после этого обработка запроса.

Защита динамических маршрутов

Динамические маршруты в Fresh создаются через синтаксис [param], например: /users/[id].tsx. Защита таких маршрутов требует проверки параметров и состояния сессии:

import { authMiddleware } from "../. ./middlewares/auth.ts";
import type { Handlers } from "$fresh/server.ts";

export const handler: Handlers = {
  async GET(req, ctx) {
    const userId = ctx.params.id;
    const authHeader = req.headers.get("Authorization");

    if (!authHeader || authHeader !== "Bearer valid-token") {
      return new Response("Unauthorized", { status: 401 });
    }

    // Получение данных пользователя
    const user = await fetchUserById(userId);
    if (!user) {
      return new Response("Not Found", { status: 404 });
    }

    return new Response(JSON.stringify(user), {
      headers: { "Content-Type": "application/json" },
    });
  },
};

Особенности:

  • Проверка ctx.params позволяет фильтровать доступ к конкретным ресурсам.
  • Каждое ветвление возвращает корректный HTTP статус, что важно для API.

Сессии и авторизация

Fresh интегрируется с cookie и сессиями без необходимости сложных библиотек. Создание защищённого маршрута с использованием сессии выглядит следующим образом:

import { getCookies, setCookie } from "https://deno.land/std/http/cookie.ts";

export async function sessionMiddleware(req: Request, ctx: any) {
  const cookies = getCookies(req.headers);
  const session = cookies["session_id"];
  
  if (!session || !(await validateSession(session))) {
    return new Response("Unauthorized", { status: 401 });
  }

  return await ctx.next();
}

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

  • getCookies извлекает cookies из запроса.
  • Функция validateSession проверяет существование и срок действия сессии.
  • Middleware можно комбинировать с аутентификацией по токену, создавая гибкую систему защиты.

Роль серверного рендеринга в защите

В Fresh страницы рендерятся на сервере, что позволяет контролировать доступ ещё до отправки HTML пользователю. Это повышает безопасность, так как чувствительные данные никогда не попадают на клиент без проверки. Пример:

import { HandlerContext } from "$fresh/server.ts";

export const handler = async (_req: Request, ctx: HandlerContext) => {
  const user = await getUserFromSession(ctx);
  if (!user) {
    return new Response("Redirecting...", {
      status: 302,
      headers: { "Location": "/login" },
    });
  }

  return ctx.render({ user });
};

Особенности серверного рендеринга:

  • Возможность редиректа до формирования страницы.
  • Передача только безопасных данных в рендер.
  • Простая интеграция с динамическими маршрутами и middleware.

Организация системы защиты

Для крупных приложений рекомендуется:

  1. Создавать отдельную папку middlewares для всех функций проверки.
  2. Использовать композицию middleware, чтобы избежать дублирования кода.
  3. Придерживаться единого формата ошибок, возвращая корректные HTTP статусы.
  4. Интегрировать проверки на уровне сервера, а не только на клиенте.
  5. Документировать доступ к каждому маршруту, чтобы было ясно, какие страницы требуют авторизации.

Итоговая структура проекта с защитой маршрутов

routes/
├─ api/
│  ├─ users/[id].ts
│  └─ login.ts
├─ dashboard.tsx
├─ index.tsx
middlewares/
├─ auth.ts
├─ session.ts

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