CRUMB a card from devarno-cloud

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).

httpOnly: true
secure: url.protocol === "https:"
sameSite: "lax"
path: "/"
maxAge: MERIDIAN_SESSION_TTL_SECONDS (default 28800 = 8h)
domain: <unset> // host-bound to stratt.dev exactly

Omitting 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 --> Handoff

Session 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 of admin | member | child; defaults to member if 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