Sessions in pages
Web resolves the signed-in user before loaders run and ships it to the browser in the hydration payload’s optional session key. Web never imports auth: it accepts any resolver through web.session, and @warlock.js/auth’s pageSession() fits.
Wire it
Section titled “Wire it”import { pageSession } from "@warlock.js/auth";import type { WebConfigurations } from "@warlock.js/web";import type { User } from "app/users/models/user";import { userSessionResource } from "app/users/resources/user-session.resource";
export default { session: pageSession({ project: (user: User) => userSessionResource(user) }),} satisfies WebConfigurations;project is required: a model never crosses the wire by default. Only what it returns reaches the browser.
Read the user
Section titled “Read the user”useUser() returns the projection, or null for a guest. It works in server render and in the browser, and components re-render on login and logout.
import { useUser } from "@warlock.js/web";
export function Greeting() { const user = useUser();
return user ? <span>Hi {user.name}</span> : <a href="/login">Sign in</a>;}Type it by augmenting SessionRegistry. Without it, user is { id: string | number } and model is unknown.
import type { User } from "app/users/models/user";import type { UserSessionOutput } from "app/users/resources/user-session.resource";
declare module "@warlock.js/web" { interface SessionRegistry { user: UserSessionOutput; model: User; }}Guard a page
Section titled “Guard a page”Use requireUser as page middleware:
import { requireUser } from "@warlock.js/web/session";
export const config = { route: "/account", middleware: [requireUser({ loginPath: "/login" })],};- A guest gets a 302 to
loginPath?redirect=<request.url>. The login path is locale-prefixed when locale routing prefixes the request locale. Data navigations follow the redirect, so client navigation lands on the login page too. - The query name is
redirect, orauth.pageAuth.returnUrlParamwhen you set it. - A request carrying an
Authorizationheader, or an app with nologinPath(option orauth.pageAuth.loginPath), gets a 401 instead. userTyperestricts to one user type andwhen(model)adds a check (it may be async). A failure is a 403, not a redirect.
requireUser({ userType: "admin", when: model => model.isActive });Loader form
Section titled “Loader form”Pass the loader context to read the model and guard from a loader. A guest throws a redirect that stops lower loaders. when must be synchronous here.
import type { PageLoaderContext } from "@warlock.js/web";import { requireUser } from "@warlock.js/web/session";
export async function loader(ctx: PageLoaderContext) { const account = requireUser(ctx, { loginPath: "/login" });
return { name: account.name };}Guest-only pages
Section titled “Guest-only pages”import { requireGuest } from "@warlock.js/web/session";
export const config = { route: "/login", middleware: [requireGuest({ to: "/account" })] };A signed-in user is sent to the redirect query when it is a same-origin relative path, else to to (default /). safeRedirectTarget(value) is exported to apply the same rule in your own login flow: it rejects //host, /\host, absolute URLs, javascript: and control characters.
Guests and the page cache
Section titled “Guests and the page cache”A guest render carries session: { user: null } and stays cacheable. A signed-in request is never served from or stored in the page cache: it carries a cookie, resolving the user marks the response auth-derived (private, no-store), and a renewal sets a cookie.
Renewal and logout
Section titled “Renewal and logout”Renewal is on by default and reactive: an expired or missing access cookie plus a valid refresh cookie mints a new pair before the response headers are written, so activity keeps the session alive. Do not call the resolver from streamed or deferred work; a late resolution throws in development and logs in production.
Log out with a POST action: authService.logout(user, accessToken, refreshToken), then authService.clearSessionCookies(response). Call clearPrefetchCache() before navigateTo/refresh() after login or logout.
Prefetch identity
Section titled “Prefetch identity”Each prefetch entry is tagged with the identity current when it was fetched (user?.id ?? null) and refused for a different one. When the identity in a payload changes, the client clears the prefetch cache automatically.
See Sessions for pages for the auth side.