Workflow
Authentication Flow
How sign-in, session refresh, and per-request authorization actually work inside the app — end to end.
Provider
Supabase Auth
Cookie-based SSR · JWT access tokens
Session TTL
8 hours
Idle timeout · refresh rotates on use
Roles
9
Owner → Auditor → External Vendor
Rate limit
120 / min
Per IP · sliding window
Sign-in
- User submits email + password to Supabase Auth (
POST /auth/v1/token, grant typepassword). - Supabase verifies credentials and sets two
HttpOnly,Secure,SameSite=Laxcookies:sb-access-token(short-lived JWT) andsb-refresh-token(rotating). - Failed attempts advance the progressive lockout counter (see below).
- Browser is redirected to
/dashboard; middleware re-validates and refreshes the session on the first request.
Progressive Lockout
Repeated failures for the same email + IP pair escalate through four lock windows. The counter resets after a successful sign-in or 24 hours of inactivity.
Per-Request Context
Every authenticated API route begins with requireOrgContext(), which returns the server-trusted identity:
userId— Supabase user id (UUID).userEmail/userName— profile data joined fromUser.orgId— the active organization the user has selected.role— one of nine roles on the membership row.
The context is re-derived per request from the cookie — it is never accepted from the client body or headers. If the session is missing or expired the handler returns 401 immediately, before any RBAC or DB call.
Authorization (RBAC)
Handlers call requirePermission(ctx, action, entity) which consults a central role × entity matrix. The nine roles in descending privilege:
- Owner · Admin — full org administration.
- Compliance Officer · Risk Manager — cross-framework read/write.
- Control Owner — write on owned entities; read elsewhere.
- Member · Viewer — everyday contributor / read-only.
- Auditor — scoped read of evidence packages and audit trail.
- External Vendor — questionnaire responses only.
Denied actions return 403 with a machine-readable error code so the UI can render the correct empty or disabled state. Permission misses do not leak whether the entity exists.
Session Hygiene
- Idle timeout — 8 hours; the next request after timeout forces re-auth.
- Refresh rotation — every use of a refresh token issues a new pair and invalidates the old.
- Explicit sign-out — revokes the refresh token server-side and clears both cookies.
- Device revocation — Supabase Auth session list powers per-device sign-out from account settings.
What we never do
localStorage. No bearer tokens are sent to third-party origins. Passwords are never logged, hashed locally, or passed to our own backend — they go directly to Supabase Auth.Error Reference
401 unauthenticated— missing / invalid session cookie.401 session_expired— cookie present but past TTL; client should re-auth.403 forbidden— authenticated but role lacks the permission.403 wrong_org— request targets an entity outside the active org.423 locked— account in a progressive lockout window.429 rate_limited— per-IP or per-endpoint budget exceeded.