Sessions

The session helper on server events reads and writes a signed cookie. The value is signed with HMAC-SHA256 using the TORPOR_SESSION_SECRET environment key, so it can't be forged or tampered with. Add the secret to your environment — or to a .env file in dev and preview:

TORPOR_SESSION_SECRET=some-long-random-string

Reading and writing

A login action sets the session; logout destroys it. Use regenerate at login — it mints a fresh session id, so a cookie that was set before the privilege change can't be reused:

import { type PageServerEndPoint, ok } from "@torpor/build";

export default {
	actions: {
		login: async (event) => {
			const { email, password } = await readForm(event.request, {
				email: "",
				password: "",
			});
			const user = await checkPassword(email, password);
			if (!user) return unauthorized();
			await event.session.regenerate({ userId: user.id });
			return ok();
		},
		logout: async (event) => {
			await event.session.destroy();
			return ok();
		},
	},
} satisfies PageServerEndPoint;

Reading a session returns its data, or undefined when there is no cookie, the signature doesn't match, or the session has expired:

const session = await event.session.get();
if (session) {
	const userId = session.userId;
}

Checking users in a hook

Read the session in a root _hook.server.ts and store the user on appData, so every layout, page and endpoint below it can use it without reading the cookie again:

// routes/_hook.server.ts
import { type ServerHook } from "@torpor/build";
import { getUser } from "@/lib/auth";

export default {
	enter: async (event) => {
		const session = await event.session.get();
		if (session) {
			event.appData.user = await getUser(session.userId);
		}
	},
} satisfies ServerHook;

Revocation with a database

Sessions live entirely in the cookie, so they can't be revoked individually. When you need that, store the session's id in your database, check it on each authed request, and delete the row to revoke:

enter: async (event) => {
	const session = await event.session.get();
	if (session) {
		const record = await db.sessions.find(session.id);
		if (!record || record.revoked) {
			await event.session.destroy();
			return;
		}
		event.appData.user = record.user;
	}
},

The id is stable across set calls, so it can be used as a database key, and regenerate mints a new one — store the new id at login.

Limits

  • Session data must be JSON-safe, and small enough to fit in a cookie (4KB) along with the id, expiry and signature
  • By default a session expires after 30 days; pass { maxAge: seconds } to set or regenerate to change it
  • The cookie is named torpor-session, and is HttpOnly, Secure and SameSite=Lax
  • Rotating TORPOR_SESSION_SECRET invalidates all existing sessions