Application authentication

MAIL-73 adds the Inbox Agent application principal. 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.

User

Migration 0002_users.sql creates inbox_agent.users:

ColumnNotes
user_idUUID primary key
identity_subjectUnique local subject (not a provider mailbox 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.

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.