TIP
This page mirrors .agents/auth.md from the repository. Edit it at the source, not here.
Auth Patterns
Architecture
- Server: Better Auth handles
/auth/*on the Hono server (apps/server) - Client:
authClientfrom@saasweave/auth(nanostore-based, client-side only) - SSR:
$getUserserver function resolves session from request headers, forwardsSet-Cookiefor refresh - Cookies: Prefer Better Auth defaults for reusable projects —
SameSite=Laxsupports OAuth and other redirect-based flows without custom per-project overrides
Query Pattern
Auth user query is defined in @saasweave/auth:
getAuthUserQueryOptions()— staleTime 5 min, gcTime 10 min, refetchOnWindowFocus "always"- Root route
beforeLoadprefetches auth (non-blockingprefetchQuery) - Auth-guarded routes use blocking
ensureQueryData
Route Guards
Protected routes ((auth)/ layout group)
beforeLoadcallsensureQueryData(getAuthUserQueryOptions())withrevalidateIfStale: true- No user → redirects to
/sign-in?redirect=<current-path> - Client-side
useEffectre-checks auth for session expiry
Guest-only routes ((guest)/ layout group)
- Authenticated users → redirected to stored redirect path
?redirectparam validated against route tree (sanitized)
Middleware
Two auth middlewares exist for different sensitivity levels:
| Middleware | Behavior | Use case |
|---|---|---|
authMiddleware | Uses cached session (5-min cookie cache) | Normal protected pages |
freshAuthMiddleware | Hits DB, bypasses cache | Sensitive operations (password change, etc.) |
Both set 401 status and throw on unauthorized.
Schema Extension
When extending the Better Auth user or session schema, update all three layers together:
- Server auth config: add the field under
additionalFieldsinpackages/auth/src/index.ts - Drizzle schema: add the matching column in
packages/db/src/schema/auth.schema.ts - Client inference: keep
packages/auth/src/react/auth-client.tsusinginferAdditionalFields<typeof auth>()so custom fields stay typed on the client
For DB-backed auth fields, also generate and apply a Drizzle migration after the schema change.
Gotchas
- Cross-domain auth setups still require deliberate cookie/domain/CORS configuration even with Better Auth defaults
SameSite=Strictis usually too brittle for OAuth, email links, and other redirect-based auth flows- The auth query uses
refetchOnWindowFocus: "always"for cross-tab session sync - Better Auth custom fields are not complete if you only add the DB column; the auth config and client inference must be updated too
- In this repo, keep the main Drizzle relation graph in
packages/db/src/schema/relations.tswithdefineRelations(). Auth tables may add their owndefineRelationsPart()inpackages/db/src/schema/auth.schema.ts, and that part must be merged after the main relations inpackages/db/src/index.ts.