Sessions
Keep who the user is on the server, and put only a signed id in a cookie.
createSessionManager keeps session data on the server and puts only an opaque id in a signed cookie. The cookie is HttpOnly, Secure and SameSite=Lax, and on https its name starts with __Host-, so a sibling subdomain cannot plant or overwrite it. A cookie whose signature does not verify counts as no cookie.
The secret comes from the environment, checked when the app starts.
import { defineEnv } from "@shadow-js/dusk/server";
export const env = defineEnv({
SESSION_SECRET: { type: "string", minLength: 32, secret: true },
});
import { createSessionManager } from "@shadow-js/dusk/server";
import { env } from "./env";
const { SESSION_SECRET } = env.parse(process.env).all;
export const sessions = createSessionManager({
secrets: [SESSION_SECRET],
cookieName: "__Host-session",
});
Read and write the session in any route, loader or server function with sessions.get(event).
import { json, type RequestEvent } from "@shadow-js/dusk/server";
import { sessions } from "../../session";
export const GET = async (event: RequestEvent) => {
const session = await sessions.get(event);
const visits = (session.get<number>("visits") ?? 0) + 1;
session.set("visits", visits);
return json({ visits });
};
import { json, type RequestEvent } from "@shadow-js/dusk/server";
import { sessions } from "../../session";
export const POST = async (event: RequestEvent) => {
const session = await sessions.get(event);
session.regenerate();
session.set("userId", "u1");
return json({ ok: true });
};
Call session.regenerate() when the user signs in, so an id an attacker knew before is worthless. session.destroy() signs out.
import { HttpError, json, type RequestEvent } from "@shadow-js/dusk/server";
import { sessions } from "../../session";
export const GET = async (event: RequestEvent) => {
const userId = (await sessions.get(event)).get<string>("userId");
if (userId === undefined) throw new HttpError(401, "Sign in first");
return json({ userId });
};
export const DELETE = async (event: RequestEvent) => {
(await sessions.get(event)).destroy();
return json({ ok: true });
};
Add sessions.middleware() to the handler's middleware: it saves the session and sets the cookie after the request, also when the route failed.
A session unused for 30 minutes ends, and every session ends after 7 days; the id changes every 15 minutes. All three are options. Sessions live in a bounded memory store by default; pass store to keep them in a database or Redis.
Next: configure the app. Sign-in with OpenID Connect, Auth.js and better-auth are in Authentication.