Session-based аутентификация строится вокруг идеи серверной сессии, связанной с конкретным клиентом. После успешной аутентификации сервер создаёт сессию, сохраняет её состояние и передаёт клиенту идентификатор сессии, как правило, через cookie. При каждом последующем запросе клиент автоматически отправляет этот идентификатор, а сервер использует его для восстановления контекста пользователя.
Fresh, как серверный фреймворк для Deno, изначально ориентирован на HTTP-first архитектуру и тесную работу с Web API. Это делает реализацию сессионной аутентификации максимально прозрачной и близкой к стандартам платформы.
В Fresh нет встроенного механизма сессий, как в классических MVC-фреймворках. Работа с cookies осуществляется напрямую через HTTP-заголовки.
Основные особенности:
Set-CookieCookieПример установки cookie после логина:
return new Response(null, {
status: 302,
headers: {
"Location": "/",
"Set-Cookie": "session_id=abc123; HttpOnly; Path=/; SameSite=Lax"
}
});
Ключевые параметры:
Сессионные данные никогда не хранятся в cookie целиком. Cookie содержит только идентификатор, а реальные данные располагаются на сервере.
В Fresh чаще всего используются:
Простейшая модель:
const sessions = new Map<string, { userId: string }>();
После аутентификации:
const sessionId = crypto.randomUUID();
sessions.set(sessionId, { userId });
Такой подход подходит только для разработки или single-instance окружений.
Fresh использует middleware на уровне
routes/_middleware.ts. Это ключевая точка для
восстановления пользователя из сессии.
Пример базового middleware:
import { MiddlewareHandlerContext } from "$fresh/server.ts";
export async function handler(
req: Request,
ctx: MiddlewareHandlerContext
) {
const cookie = req.headers.get("cookie");
const sessionId = cookie?.match(/session_id=([^;]+)/)?.[1];
if (sessionId) {
const session = sessions.get(sessionId);
if (session) {
ctx.state.user = session.userId;
}
}
return await ctx.next();
}
Важные моменты:
ctx.state используется для передачи данных дальше по
цепочкеПроверка аутентификации выполняется либо в middleware, либо непосредственно в обработчике маршрута.
Пример защиты страницы:
export const handler = {
GET(req, ctx) {
if (!ctx.state.user) {
return new Response(null, {
status: 302,
headers: { Location: "/login" }
});
}
return ctx.render();
}
};
Подход с редиректом предпочтителен для UI-маршрутов, тогда как
API-маршруты обычно возвращают 401 Unauthorized.
Fresh использует islands architecture, при которой большая часть логики остаётся на сервере. Это хорошо сочетается с session-based аутентификацией.
Данные пользователя передаются в JSX:
export default function Page(props) {
return (
<div>
<p>Пользователь: {props.user}</p>
</div>
);
}
И прокидываются через ctx.render:
return ctx.render({ user: ctx.state.user });
JavaScript на клиенте не имеет прямого доступа к cookie с флагом
HttpOnly, что повышает безопасность.
Выход из системы заключается в двух шагах:
Пример обработчика:
sessions.delete(sessionId);
return new Response(null, {
status: 302,
headers: {
"Location": "/login",
"Set-Cookie": "session_id=; Max-Age=0; Path=/"
}
});
Cookie с Max-Age=0 считается немедленно истёкшим.
Session-based подход уязвим к CSRF-атакам, если не применяются защитные меры.
Обязательные элементы защиты:
SameSite=Lax или SameSite=StrictOrigin и Referer для APIПример генерации CSRF-токена в сессии:
session.csrf = crypto.randomUUID();
И проверки в POST-запросе:
if (formData.get("csrf") !== session.csrf) {
return new Response("Forbidden", { status: 403 });
}
Session-based аутентификация в Fresh имеет следующие особенности:
Для production-сценариев предпочтительно:
Session-based аутентификация в Fresh:
В контексте Fresh этот подход считается базовым и наиболее естественным, так как фреймворк изначально проектировался вокруг серверного рендеринга и минимального клиентского состояния.