Meridian Session Cookie Lifecycle
meridian intermediate 5 min read
ELI5
The session cookie is a tamper-evident wristband: Meridian writes your name on it, stamps it with a hidden ink, and you flash it on every request. After eight hours the ink fades and the bouncer sends you back to airlock for a new wristband.
Technical Deep Dive
The cookie is named __stratt_meridian_session (constant in lib/airlock-handoff.ts). It is a JWT signed with HMAC-SHA256 over MERIDIAN_SESSION_SECRET (must be ≥ 32 chars — signMeridianSession throws otherwise).
Cookie Attributes
httpOnly: truesecure: url.protocol === "https:"sameSite: "lax"path: "/"maxAge: MERIDIAN_SESSION_TTL_SECONDS (default 28800 = 8h)domain: <unset> // host-bound to stratt.dev exactlyOmitting domain keeps the cookie scoped to the exact host. Switching to .stratt.dev would let subdomains read it but expand CSRF surface.
State Diagram
stateDiagram-v2 [*] --> NoCookie NoCookie --> Handoff: middleware 302 Handoff --> Minted: callback verifies EdDSA + signs HS256 Minted --> Valid: Set-Cookie returned Valid --> Valid: jwtVerify ok Valid --> Expired: exp <= now Valid --> Invalid: secret rotated / tampered Expired --> Handoff Invalid --> HandoffSession Age
Middleware computes sessionAgeMs = max(0, ttlMs - (exp - now)). The TTL is sourced from MERIDIAN_SESSION_TTL_SECONDS at runtime; if an operator lowers the TTL mid-flight, already-minted cookies report inflated ages until they wrap. The age is emitted as auth.session_age_ms on every auth.decision span — bounding the worst-case revocation SLA visible on the F9 dashboard even before the NATS revocation bridge is wired.
Verification Path
verifyMeridianSession enforces iss=https://stratt.dev, aud=meridian, alg=HS256, presence of sub and email. Any failure returns null — the middleware then redirects with reason invalid_session (cookie present) or no_cookie (cookie absent) to keep denial-reason telemetry separable.
Key Terms
sub→ User id, copied from the airlock JWT.role→ One ofadmin | member | child; defaults tomemberif missing.- HMAC-SHA256 → Symmetric MAC; both signer and verifier need the same secret.
Q&A
Q: What happens if MERIDIAN_SESSION_SECRET is shorter than 32 chars?
A: signMeridianSession throws on cookie mint, the callback returns the “Server misconfigured” 500 page, and the user never gets a cookie.
Q: Why distinguish no_cookie from invalid_session in telemetry?
A: no_cookie is normal first-visit traffic; invalid_session indicates secret rotation, tampering, or a stale browser — different signals deserve different alert thresholds.
Q: What is the cookie scope after deploy?
A: Host-only on stratt.dev. A request to app.stratt.dev will not see it; that subdomain would re-handoff through airlock and mint its own.
Examples
After secret rotation, every existing cookie’s HMAC fails verification on the next request. Each user hits middleware once, gets a redirect with auth.reason=invalid_session, bounces through airlock (already authenticated there), receives a fresh cookie, and proceeds. The dashboard shows a visible spike of invalid_session denials that decays over the deploy minute.
neighbors on the map
- Multi-Strategy Authentication debugging 401 errors across different clients
- JWT Auth & RBAC Hierarchy adding a new permission to a tool
- Chronicle JWT Claims & Dev Tokens issuing tokens from an external auth provider