Skip to content

Application authentication

MAIL-73 added the Duckbill™ application principal. MAIL-184 issues that principal after Sign in with Apple. This is not Microsoft Graph or Gmail OAuth. Provider tokens stay out of the app session and are MAIL-74's responsibility.

See ADR 0005 for the cookie and CSRF shape, and ADR 0028 / Sign in with Apple for the IdP.

User

Migration 0002_users.sql creates inbox_agent.users:

ColumnNotes
user_idUUID primary key
identity_subjectApple sub (MAIL-184) or owner ops subject. Not a mailbox provider id
statusactive or disabled
created_at / updated_atTimestamps

Row-level security is enabled. anon and authenticated are revoked when those roles exist. The table is not on the Supabase Data API.

Repository: createUserRepository in @inbox-agent/persistence.

Session

SESSION_SECRET (32+ characters, already required by loadRequiredEnv) signs an HTTP-only cookie named ia_session.

Cookie flags: HttpOnly, Path=/, SameSite=Lax, Max-Age 12 hours. Secure is set when the issuer asks for a secure cookie (preview and production).

Claims allowlist:

v, userId, iat, exp, csrf

The public session view returned to API clients is { userId, csrfToken } only. GET /api/me returns that view. The site header reads /api/me and shows the signed-in userId (or Sign in when there is no session).

Product routes

Unauthenticated /inbox, /inbox/*, and /accounts redirect to /login. Do not leave a 401 triage shell as the only UX. After Sign in with Apple — or the owner ops escape POST /api/session/owner when OWNER_SESSION_ENABLED is true — triage loads. The owner session is not a second product IdP.

There is no product sign-out route yet. The ia_session cookie expires after 12 hours (Max-Age). Clearing the cookie in the browser ends the session.

CSRF

Mutating methods (POST, PUT, PATCH, DELETE) must send X-CSRF-Token matching the csrf claim inside the signed cookie. GET/HEAD/OPTIONS skip CSRF but still require a valid session.

API contract

Every user-facing API call goes through authenticateRequest / handleProtectedApi. Missing or invalid cookies return:

{
  "data": null,
  "error": {
    "code": "UNAUTHENTICATED",
    "message": "Authentication required",
    "retryable": false
  }
}

CSRF failures return 403 with UNAUTHORIZED. Neither response includes provider tokens.

Logging

Reuse MAIL-69 redaction. cookie, token, and authorization field names are already redacted by @inbox-agent/security. Do not add a second redaction implementation.