ADR 0005 — Signed session cookie, no external IdP
Status
Accepted
Context
MAIL-73 must authenticate the Inbox Agent user and persist User. The application session is a different concern from Microsoft Graph / Gmail provider OAuth. An external identity provider was considered and rejected for Sprint 01: the first deployment is single-user-ready and must not block on Auth0, Clerk, Cognito, or Supabase Auth.
SESSION_SECRET is already required at process start (MAIL-72). MAIL-74 encrypts provider tokens in a secret store (ADR 0004). Those tokens must never appear in the application session.
MAIL-74 shipped first and claimed ADR 0004 for token key separation, so this session decision is ADR 0005.
Decision
- Do not integrate an external IdP in Sprint 01.
- Authenticate user-facing API calls with a repository-signed, HTTP-only
ia_sessioncookie (HMAC-SHA256,SESSION_SECRET). - Persist
inbox_agent.userswith a UUIDuser_idprimary key,identity_subject, status, and timestamps. - Protect mutating requests with a synchronizer CSRF token stored inside the signed session and required as
X-CSRF-Token. - Session claims are an allowlist:
v,userId,iat,exp,csrf. Provider access, refresh, and ID tokens are not accepted fields. - Unauthenticated API access returns the canonical envelope with
error.code = UNAUTHENTICATED.
Alternatives considered
- External IdP (Auth0 / Clerk / Supabase Auth): rejected for Sprint 01. Adds an operator dependency before the product has multi-user identity requirements.
- JWT access tokens in
Authorization: Bearer: rejected. Easy to confuse with provider OAuth tokens and more likely to leak into logs or frontend storage. - Server-side session table: deferred. A signed cookie is enough for a single-user-ready principal; a session store can be added later without changing the cookie name or CSRF header.
Consequences
@inbox-agent/securityissues and verifies cookies.@inbox-agent/applicationgates requests.apps/webexposes a minimal protected handler.- CSRF tokens may appear on the authenticated principal response so the UI can send the header. They are not provider secrets.
- Login UX (passkey, password, or a later IdP) is out of scope. Callers issue a session only after they already have a persisted
user_id. - MAIL-74 remains the only place provider tokens are stored, encrypted, and never copied into
ia_session.
