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)andc.set("session", session | null)via the augmentedContextVariableMap(see./context-augmentation.ts). - Default mapping uses
mapBetterAuthUserToPrincipal; opt-out viaprincipalMappingfor plugin-extended session shapes. - Records
auth.sessions_resolved_total{outcome}andauth.session_resolve_duration_msfor every request, regardless of outcome. - Failures during session resolution set
c.get("user")tonull, log atwarn, and let the request proceed — protected routes still throwAUTH_UNAUTHENTICATEDviarequireAuth()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 tomapBetterAuthUserToPrincipal.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_UNAUTHENTICATEDviarequireAuth()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-
Errorthrowables from Better-Auth are normalised viaString(err)— the middleware does not assume Better-Auth's failure modes are pureErrorinstances.