# middleware/session ## Purpose `sessionMiddleware` is the global Hono middleware that resolves every incoming request's Better-Auth session and exposes a typed `Principal` through `c.get("user")`. It is installed by `createAuthPlugin`'s `onInit` against `ctx.app` so anonymous routes keep working while protected handlers can rely on `c.get("user")` being either a real `Principal` or `null` — never undefined. ## Features - Calls `auth.api.getSession({ headers: c.req.raw.headers })` once per request and caches the result on the context. - Sets `c.set("user", principal | null)` and `c.set("session", session | null)` via the augmented `ContextVariableMap` (see `./context-augmentation.ts`). - Default mapping uses `mapBetterAuthUserToPrincipal`; opt-out via `principalMapping` for plugin-extended session shapes. - Records `auth.sessions_resolved_total{outcome}` and `auth.session_resolve_duration_ms` for every request, regardless of outcome. - Failures during session resolution set `c.get("user")` to `null`, log at `warn`, and let the request proceed — protected routes still throw `AUTH_UNAUTHENTICATED` via `requireAuth()` so the surface is unaffected. ## Usage: Basic ```ts import { sessionMiddleware } from "@suaveplan/api-plugin-auth/middleware/session"; import { betterAuth } from "better-auth"; const auth = betterAuth({ /* … */ }); app.use("*", sessionMiddleware({ auth })); ``` ## Usage: Advanced Pair the middleware with a custom mapping and a host-supplied logger so role projection and trace correlation are wired in one place: ```ts import { sessionMiddleware } from "@suaveplan/api-plugin-auth/middleware/session"; import { mapBetterAuthUserToPrincipal } from "@suaveplan/api-plugin-auth/principal/principal"; app.use( "*", sessionMiddleware({ auth, logger: ctx.logger, principalMapping: (view) => { const base = mapBetterAuthUserToPrincipal(view); const session = view.session as { roles?: string[] }; return { ...base, roles: [...base.roles, ...(session.roles ?? [])] }; }, }) ); ``` ## API Reference ### `function sessionMiddleware(options): MiddlewareHandler` `options.auth` is required; `options.principalMapping` and `options.logger` are optional. The returned middleware is async and mutates `c`'s context variables — it never throws. ### `interface SessionMiddlewareOptions` - `auth: Auth` — the Better-Auth instance. - `principalMapping?: PrincipalMapping` — defaults to `mapBetterAuthUserToPrincipal`. - `logger?: LoggerLike` — defaults to a no-op logger. ### `type PrincipalMapping = (view: BetterAuthSessionView) => Principal` ### Module augmentation `./context-augmentation.ts` declares `interface ContextVariableMap { user: Principal | null; session: BetterAuthSessionResult }` so the typed `c.get("user")` lookups work without a cast in every consumer. ## Implementation Notes - The middleware does NOT throw on failure — anonymous routes must keep working even when Better-Auth's session store is degraded. Protected routes are responsible for raising `AUTH_UNAUTHENTICATED` via `requireAuth()` only when they need an authenticated user. - `performance.now()` is the duration source; the value is recorded in milliseconds (matching the histogram's declared unit). - The outcome label is bounded to `"authenticated"` / `"anonymous"` / `"error"` so dashboards can rely on a fixed cardinality. - Non-`Error` throwables from Better-Auth are normalised via `String(err)` — the middleware does not assume Better-Auth's failure modes are pure `Error` instances.