Lucia

Validate session cookies in Elysia

CSRF protection must be implemented when using cookies and forms. This can be easily done by comparing the Origin and Host header.

We recommend creating a middleware to validate requests and store the current user inside Context with App.derive(). You can get the cookie name with Lucia.sessionCookieName and validate the session cookie with Lucia.validateSession(). Make sure to delete the session cookie if it's invalid and create a new session cookie when the expiration gets extended, which is indicated by Session.fresh.

// src/middleware.ts
import { verifyRequestOrigin } from "lucia";

import type { User, Session } from "lucia";

const app = new Elysia().derive(
	async (
		context
	): Promise<{
		user: User | null;
		session: Session | null;
	}> => {
		// CSRF check
		if (context.request.method !== "GET") {
			const originHeader = context.request.headers.get("Origin");
			// NOTE: You may need to use `X-Forwarded-Host` instead
			const hostHeader = context.request.headers.get("Host");
			if (!originHeader || !hostHeader || !verifyRequestOrigin(originHeader, [hostHeader])) {
				return {
					user: null,
					session: null
				};
			}
		}

		// use headers instead of Cookie API to prevent type coercion
		const cookieHeader = context.request.headers.get("Cookie") ?? "";
		const sessionId = lucia.readSessionCookie(cookieHeader);
		if (!sessionId) {
			return {
				user: null,
				session: null
			};
		}

		const { session, user } = await lucia.validateSession(sessionId);
		if (session && session.fresh) {
			const sessionCookie = lucia.createSessionCookie(session.id);
			context.cookie[sessionCookie.name].set({
				value: sessionCookie.value,
				...sessionCookie.attributes
			});
		}
		if (!session) {
			const sessionCookie = lucia.createBlankSessionCookie();
			context.cookie[sessionCookie.name].set({
				value: sessionCookie.value,
				...sessionCookie.attributes
			});
		}
		return {
			user,
			session
		};
	}
);

This will allow you to access the current user with Context.user.

app.get("/", async (context) => {
	if (!context.user) {
		return new Response(null, {
			status: 401
		});
	}
	// ...
});