Model and Sessions
Model and Sessions at the Askr server boundary, with validated input and explicit failure states.
Example
Resolve identity at the request boundary and express authorization as reusable requirements instead of component-only checks.
import {
createAuth,
requirePermission,
requireUser,
} from '@askrjs/auth';
export const auth = createAuth({
sessions: sessionStore,
principals: principalStore,
});
export const manageProjects = [
requireUser(),
requirePermission('projects:write'),
];Authentication context
AuthContext<P, S> is generic over your own Principal and AuthSession subtypes, so custom claims you add via the shared Claim base show up typed on ctx.auth.principal and ctx.auth.session rather than as untyped extras. Beyond the principal and session, it also carries tenant (string | null) and an optional scopes list, which is what requireScope() checks against. It's the return value of AuthResolver.resolve() and the only object requirement functions and route handlers need to look at.
Session storage
SessionStore<S> has exactly one method to implement: get(id, {request, signal}), returning S | null synchronously or as a promise. You pass it in as AuthOptions.sessions, and createAuth calls it during resolve() with the id it pulls from the configured sessionCookie. There's no built-in in-memory or database-backed implementation shipped with @askrjs/auth - you own storage, TTL, and any indexing it needs.
Cookies
AuthOptions.sessionCookie is just the cookie name createAuth reads to find a session id; @askrjs/auth never sets or clears cookies itself. Writing them is a @askrjs/server concern - ctx.setCookie(response, name, value, {httpOnly, sameSite, secure}) and ctx.clearCookie() operate on the outgoing Response and don't touch session storage or validate anything. Keeping the two apart means a route can set a cookie during login without @askrjs/auth needing to know the request ever happened.
Sign out and expiry
AuthSession carries optional expiresAt and revokedAt timestamps. createAuth checks both itself, against AuthOptions.clock() (Date.now by default, overridable for tests), before treating a returned session as authenticated - a session your SessionStore.get() hands back past its expiresAt or with revokedAt set is rejected by the resolver even if the store still returns it. Your store only needs to return null for sessions it wants gone entirely (rotated out, deleted); it doesn't have to filter on expiry or revocation itself, though there's nothing stopping it from doing that too. Sign-out is your store's responsibility: mark the row revoked or delete it, and the next get() for that id should return null (or an already-expired/revoked session, which resolve() will now also reject on its own) so resolve() falls back to an unauthenticated context.