Files
skills/docs/api-stack/api/api-plugin-auth/src/middleware/session.md
2026-05-29 08:20:56 +01:00

3.6 KiB

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

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:

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.