Cross-cutting · verified · auth-and-active-context

Auth & JWT Lifecycle

When a user logs in, how do tokens get issued, refreshed, and revoked — and what's the real contract between FuzzyCode and every sibling service?

Last verified 2026-04-17 against code
Slug auth-and-active-context
Audience engineer
Participating services FuzzyCode (broker) · Pages · ImageBuddy · SoundBuddy · SpriteBuddy · UploaderBuddy · SimpleGPT · Supabase

§1 The shape

FuzzyCode is the auth broker. Every sibling service trusts tokens issued by FuzzyCode. Supabase sits outside the platform as the identity provider; FuzzyCode mediates between it and the browser. Sibling services never talk to Supabase directly — they verify tokens against FuzzyCode's JWKS.

sequenceDiagram autonumber participant U as 👤 Browser participant F as FuzzyCode
(broker) participant SB as Supabase
auth.users participant R as Redis
(refresh lock+cache) participant S as Sibling service
(Pages / Images / …) rect rgb(237, 217, 192) Note over U,SB: LOGIN — broker mediates between browser and Supabase U->>+F: POST /auth/login
email · password F->>+SB: password grant SB-->>-F: { access_token (JWT), refresh_token } F->>F: set_broker_auth_cookies()
7+ HttpOnly cookies · see §3 F-->>-U: 200 · Set-Cookie (×N) end rect rgb(230, 169, 94) Note over U,S: NORMAL REQUEST — sibling verifies against broker JWKS U->>+S: GET /some/route
Cookie: jwt_token · fc_active_ctx S->>+F: GET /auth/active-context/.well-known/jwks.json F-->>-S: { keys: [RSA JWK] } · (empty array if AC flag off) S->>S: verify JWT via JWKS-first (RS256)
fall back to HS256 secret S-->>-U: 200 · response end rect rgb(199, 217, 232) Note over U,R: REFRESH — Redis lock + cache prevents thundering herd U->>+F: POST /auth/session/refresh F->>+R: SETNX refresh_lock_key (ex 10s) alt lock acquired F->>+SB: refresh_token grant SB-->>-F: new access_token + refresh_token F->>R: SET refresh_result_key (ex 5s) F->>F: set_broker_auth_cookies() F-->>U: 200 · new Set-Cookie else lock contended F->>R: poll refresh_result_key (≤ 1.6s) alt cached result arrived F-->>U: 200 · reuses sibling's fresh tokens else still missing F-->>-U: 409 REFRESH_IN_PROGRESS end end deactivate R end rect rgb(253, 236, 234) Note over U,F: REVOCATION — realtime projection invalidates sessions F->>F: runtime_controls_status subscribes
to child/parent projections Note over F: on high_watermark bump,
actor hydration fails (blocked)
→ subsequent requests 401 end
Login — broker mints cookies from Supabase grant
Request — sibling verifies via JWKS (or HS256 fallback)
Refresh — Redis-guarded; thundering-herd safe
Revoke — projection-driven; sessions invalidated in place

§2 JWT verification chain (at every sibling service)

Every sibling service runs this exact ladder. Order matters: JWKS-first favors asymmetric keys (rotatable); HS256 fallback keeps legacy secrets working during rotation. Expired tokens are sometimes allowed on public routes (allow_expired=True) but always rejected on protected routes.

flowchart TD classDef step fill:#fdf6ea,stroke:#683c06,color:#111 classDef ok fill:#e8f6ec,stroke:#2f7a3e,color:#111 classDef fail fill:#fdecea,stroke:#b8432e,color:#111 classDef note fill:#eaf3ff,stroke:#1d58b1,color:#111 A[Incoming request
Cookie: jwt_token] --> B[Fetch JWKS
from FuzzyCode] B --> C{Token kid
present in JWKS?} C -->|yes| D[Verify asymmetric
RS256 / ES256 / EdDSA] C -->|no| E[HS256 fallback
MY_SUPABASE_JWT_SECRET
or SUPABASE_JWT_SECRET_KEY] D -->|valid| F[Extract claims
sub · role · exp · session_version] E -->|valid| F D -->|invalid| X[401 Unauthorized] E -->|invalid| X F --> G{Protected route?} G -->|yes, and exp < now| X G -->|no, or exp > now| H[Check active-context token
if present and AC_ENABLED] H -->|valid AC + matches sub| I[Authorized] H -->|AC invalid or mismatch| X H -->|AC absent and AC_ENABLED=false| I class A,B,D,E,F,H step class I ok class X fail

§3 Cookie inventory

Twelve cookies in play at login. Grouped by role; TTLs are from code.

Dead claim correction: WIP_DOCS/auth-context-analysis.md describes the system as user_id/user_name + jwt_token validated by @jwt_required. That document is retired — it describes pre-broker auth. The real system is the broker cookie set above plus server-side VerifiedActor hydration.

§4 Environment state

UAT · 2026-04-17

Active-context enabled. fc_active_ctx cookie minted at login + refresh. JWKS endpoint returns RSA key. Sibling services enforce active-context claims (e.g., sessions gated on session_version, child-scoped permissions). Entitlements flow from the active-context token rather than raw JWT claims.

PROD · 2026-04-17

Active-context disabled across every service .replit (ACTIVE_CONTEXT_ENABLED="false"). JWKS returns { keys: [] }. Sibling services gate on raw JWT claims — meaning child-direct sessions without SUBSCRIPTION:ACTIVE / CREATE_IMAGE:FULL cannot use media services (Images/Sounds/Sprites). The active-context path exists and is live in UAT; prod cutover is planned but not yet in progress.

§5 Refresh contention — what happens when 5 tabs all wake up

Only one refresh can hold the Supabase refresh-token at a time (Supabase rotates refresh tokens on each use). The broker serializes via Redis and shares the result, so concurrent tabs never burn each other's refresh.

stateDiagram-v2 direction LR [*] --> CheckCache: POST /auth/session/refresh CheckCache --> ServeCached: refresh_result_key exists CheckCache --> TryLock: no cached result TryLock --> HoldLock: SETNX success TryLock --> PollCache: SETNX fail (someone else has lock) HoldLock --> CallSupabase: exchange refresh_token CallSupabase --> StoreResult: success CallSupabase --> Respond401: refresh revoked StoreResult --> Respond200: cookies updated PollCache --> ServeCached: result arrived (≤ 1.6s) PollCache --> Respond409: timeout (REFRESH_IN_PROGRESS) ServeCached --> Respond200 Respond200 --> [*] Respond401 --> [*] Respond409 --> [*]

Evidence: auth.py:4160-4321 (handler), broker.py:374-381 (key helpers). Client-side freshness supervisor is broker_session_freshness_model.js:8-50 — it throttles refresh requests from a single tab.

§6 Verification pointers