BetterAuth Session Cookie & Edge Middleware
so1 intermediate 4 min read
ELI5
The middleware is a bouncer at the door of every protected page: it peeks for the so1.session_token wristband. No wristband, you go to /auth/login. Already wristbanded but trying to re-enter the line, you get nudged to /dashboard.
Technical Deep Dive
Cookie Name Selection
src/middleware.ts chooses the cookie based on NODE_ENV:
| Env | Cookie Name |
|---|---|
| development | so1.session_token |
| production | __Secure-so1.session_token |
The __Secure- prefix is enforced when useSecureCookies: true in BetterAuth — it requires HTTPS and rejects plaintext setting.
Decision Flow
flowchart TD start[request] --> match{matcher hit?} match -- no, asset/_next --> pass[pass through] match -- yes --> read[read SESSION_COOKIE] read --> auth{authenticated?} auth -- no --> prot{protected prefix?} prot -- no --> pass2[pass through] prot -- yes --> login["redirect /auth/login?callbackUrl=…"] auth -- yes --> isauth{is /auth/login or /auth/signup?} isauth -- yes --> dash[redirect /dashboard] isauth -- no --> pass3[pass through]Protected Prefixes
Hard-coded in PROTECTED_PREFIXES: /dashboard, /content, /catalog, /workflows, /jobs, /agents, /pathfinder, /elevator, /branding, /social, /subsystems, /settings.
Matcher
The config.matcher excludes Next.js internals (_next) and common static assets (html, css, images, fonts, csv/xlsx/zip, webmanifest), and explicitly opts in (api|trpc)(.*).
Implementation Note
ADR-001 specifies Clerk OIDC; the codebase has since migrated to BetterAuth. The cookie-based check above is the live implementation in src/middleware.ts.
Key Terms
- Edge runtime → Next.js middleware runs in V8 isolates close to the user, not Node.
__Secure-prefix → browser-enforced rule that the cookie must haveSecureand was set over HTTPS.- callbackUrl → query param the login page reads to bounce the user back after success.
Q&A
Q: Does the middleware actually validate the session token’s signature? A: No — it only checks for the cookie’s presence. Signature/expiry validation is the BFF’s job, so a stale cookie still reaches a protected page and gets a 401 from data-fetching hooks.
Q: A request to /api/bff/jobs without a cookie — what happens?
A: The matcher includes /api, but since /api is not in PROTECTED_PREFIXES the request passes through; the BFF then rejects with 401.
Q: How do I add a new authed section?
A: Append the prefix to PROTECTED_PREFIXES in src/middleware.ts.
Examples
Visiting /jobs while logged out redirects to /auth/login?callbackUrl=%2Fjobs. After successful sign-in, BetterAuth sets the cookie; the next navigation to /jobs passes the bouncer.
neighbors on the map
- Multi-Strategy Authentication debugging 401 errors across different clients
- Airlock JWT Handoff & Session Cookies debugging login loops or session expiry
- LORE RBAC & Airlock Auth Flow implementing authentication in a new LORE page
- Auth-First WebSocket Handshake debugging AUTH_REQUIRED errors on the relay