OAuth 2.0 интеграция

Fresh — фреймворк для Deno, ориентированный на серверный рендеринг, островную архитектуру и минимальное использование клиентского JavaScript. Интеграция OAuth 2.0 в Fresh-­приложениях опирается на стандартные HTTP-механизмы, Web API Deno и строгую модель модулей без node_modules. Это упрощает контроль зависимостей, но требует точного понимания протокола OAuth 2.0 и жизненного цикла запросов.

OAuth 2.0 используется для делегированной авторизации: приложение получает ограниченный доступ к ресурсам пользователя через сторонний провайдер (Google, GitHub, Microsoft и др.), не работая напрямую с его учетными данными.


Архитектурные элементы OAuth 2.0 в Fresh

Классическая схема OAuth 2.0 включает следующие роли:

  • Resource Owner — пользователь
  • Client — Fresh-приложение
  • Authorization Server — сервер авторизации провайдера
  • Resource Server — API провайдера

В Fresh-приложении ключевые точки интеграции:

  • маршруты авторизации (/login, /oauth/callback);
  • серверная логика обработки токенов;
  • хранилище сессий или токенов;
  • middleware для защиты маршрутов.

Тип потока: Authorization Code Flow

Для серверных приложений Fresh используется Authorization Code Flow. Он безопасен, поддерживает refresh-токены и не требует хранения секретов на клиенте.

Основные шаги:

  1. Перенаправление пользователя на сервер авторизации.
  2. Возврат authorization_code в callback-маршрут.
  3. Обмен кода на access_token.
  4. Использование токена для доступа к API.

Маршрут начала авторизации

В Fresh маршруты определяются файловой структурой. Начало OAuth-процесса обычно реализуется в серверном обработчике:

// routes/login.ts
import { Handlers } from "$fresh/server.ts";

export const handler: Handlers = {
  GET() {
    const params = new URLSearchParams({
      client_id: Deno.env.get("OAUTH_CLIENT_ID")!,
      redirect_uri: "https://example.com/oauth/callback",
      response_type: "code",
      scope: "openid profile email",
      state: crypto.randomUUID(),
    });

    return Response.redirect(
      `https://provider.com/oauth/authorize?${params}`,
      302,
    );
  },
};

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

  • state используется для защиты от CSRF.
  • redirect_uri должен совпадать с настройками у провайдера.
  • Перенаправление выполняется сервером, без клиентского JS.

Callback-маршрут и обмен кода на токен

Callback-маршрут принимает code и state, проверяет корректность и запрашивает токен:

// routes/oauth/callback.ts
import { Handlers } from "$fresh/server.ts";

export const handler: Handlers = {
  async GET(req) {
    const url = new URL(req.url);
    const code = url.searchParams.get("code");

    const tokenResponse = await fetch("https://provider.com/oauth/token", {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
      },
      body: new URLSearchParams({
        grant_type: "authorization_code",
        code: code!,
        redirect_uri: "https://example.com/oauth/callback",
        client_id: Deno.env.get("OAUTH_CLIENT_ID")!,
        client_secret: Deno.env.get("OAUTH_CLIENT_SECRET")!,
      }),
    });

    const tokens = await tokenResponse.json();

    // сохранение токенов
    return new Response(null, {
      status: 302,
      headers: { Location: "/" },
    });
  },
};

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

  • Используется встроенный fetch.
  • Нет сторонних HTTP-клиентов.
  • Все переменные окружения доступны через Deno.env.

Хранение токенов и сессий

Fresh не навязывает механизм сессий. Возможные подходы:

HTTP-cookies

  • Хранение access-токена в HttpOnly cookie.
  • Подходит для SSR и защищённых маршрутов.
headers.append(
  "Set-Cookie",
  `access_token=${token}; HttpOnly; Secure; Path=/`,
);

Server-side storage

  • Redis, KV-хранилище Deno, база данных.
  • Cookie содержит только session ID.

Deno KV

Нативное хранилище для Fresh:

const kv = await Deno.openKv();
await kv.set(["session", sessionId], tokens);

Защита маршрутов через middleware

Fresh поддерживает middleware на уровне маршрутов:

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

export async function handler(
  req: Request,
  ctx: MiddlewareHandlerContext,
) {
  const cookie = req.headers.get("cookie");
  if (!cookie?.includes("access_token")) {
    return Response.redirect("/login", 302);
  }
  return await ctx.next();
}

Такой middleware автоматически защищает все маршруты ниже по иерархии.


Получение данных пользователя

После получения access_token выполняется запрос к API провайдера:

const userResponse = await fetch("https://provider.com/userinfo", {
  headers: {
    Authorization: `Bearer ${accessToken}`,
  },
});
const profile = await userResponse.json();

Данные профиля могут быть:

  • сохранены в базе;
  • использованы для создания локального пользователя;
  • связаны с существующей учетной записью.

Обновление access-токена

Если провайдер поддерживает refresh-токены:

await fetch("https://provider.com/oauth/token", {
  method: "POST",
  body: new URLSearchParams({
    grant_type: "refresh_token",
    refresh_token,
    client_id,
    client_secret,
  }),
});

Обновление выполняется сервером, прозрачно для пользователя.


Безопасность интеграции

Обязательные меры:

  • проверка state параметра;
  • использование HTTPS;
  • хранение client_secret только на сервере;
  • минимальные scope;
  • короткий срок жизни access-токена.

Fresh, за счёт отсутствия клиентской логики по умолчанию, снижает риск утечки токенов через XSS.


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

OAuth-логика полностью серверная. Острова (islands/) используются только для UI-состояний:

  • отображение статуса авторизации;
  • кнопка выхода;
  • асинхронные действия после SSR.

Авторизация не требует гидратации страницы, что сохраняет ключевое преимущество Fresh — минимальный JavaScript на клиенте.


Расширяемость и мультипровайдерность

Интеграция нескольких OAuth-провайдеров достигается абстракцией конфигурации:

type ProviderConfig = {
  authorizeUrl: string;
  tokenUrl: string;
  scope: string;
};

Маршруты Fresh легко масштабируются под Google, GitHub, Discord и корпоративные OAuth-серверы без изменения архитектуры.


Роль OAuth 2.0 в экосистеме Fresh

OAuth 2.0 органично вписывается в философию Fresh:

  • сервер-ориентированная логика;
  • нативные Web API;
  • минимальная зависимость от библиотек;
  • предсказуемый контроль безопасности.

Интеграция остаётся прозрачной, расширяемой и полностью соответствующей стандартам веб-платформы.