Askr
Server & APIs

Authentication

Use authentication with the current published Askr packages.

  • @askrjs/auth0.0.3
  • @askrjs/server/auth0.0.3

How to use authentication

Resolve identity at the request boundary and express authorization as reusable requirements instead of component-only checks.

  1. Import the published @askrjs/auth and @askrjs/server/auth entrypoint.
  2. Keep authentication configuration next to the component, route, or server composition root that owns it.
  3. Handle pending, unavailable, cancellation, and failure states, then verify the production path.
import {
  createAuth,
  requirePermission,
  requireUser,
} from '@askrjs/auth';

export const auth = createAuth({
  sessions: sessionStore,
  principals: principalStore,
});

export const manageProjects = [
  requireUser(),
  requirePermission('projects:write'),
];

Identity model

A Principal (id, subject?, roles?, permissions?) and an AuthSession (id, subject, expiresAt?, revokedAt?) are separate types in @askrjs/auth, both extending a Claim base of Record<string, unknown> so you can attach whatever custom fields your provider sends. createAuth(options) builds an AuthResolver whose resolve(request) returns one AuthContext per request, bundling authenticated, principal, session, tenant, and an optional scopes list into a single object. Route handlers and requirement functions both read from that same AuthContext, so there's one shape to reason about regardless of how the principal was established.

Session boundary

AuthOptions accepts a sessions store, a principals store, a jwt validator, a jwtCookie {name, validator} pair, a tenant resolver, a sessionCookie name, and a clock function - you wire in whichever combination matches your app. During resolve(), createAuth picks whichever configured source actually matches the incoming request (a session cookie, a bearer JWT, a JWT cookie) and normalizes the result to the same AuthContext shape either way. Nothing about session storage lives in @askrjs/server itself; it's entirely delegated to the SessionStore and PrincipalStore you pass in.

Browser and API clients

Because AuthOptions can hold both a sessionCookie-based session lookup and a jwt or jwtCookie validator at the same time, one createAuth() call can serve cookie-carrying browsers and bearer-token API clients without standing up two separate resolvers. @askrjs/server exposes the resolved value as ctx.auth on every request, so route code written against ctx.auth.principal doesn't need to know which path produced it. That's also what lets the same requirePermission()/requireUser() checks apply uniformly to both kinds of caller.

Failure behavior

A lookup that fails to identify anyone resolves to authenticated: false with principal and session set to null rather than throwing, so handlers can inspect ctx.auth without try/catch. Turning that into an actual accept/reject decision is the job of the requirement functions covered under Authorization Requirements, which map it to an AuthDecision of {allowed: false, reason: 'unauthenticated' | 'forbidden' | 'already_authenticated'}. Malformed tokens are the one case that does throw, surfacing as a JwtValidationError from the JWT validator rather than silently falling through to an anonymous context.