# ADR 0007: Cookie sessions, Argon2id, OIDC-ready identity model - Status: accepted - Date: 2026-07-04 ## Context Kickoff decisions: self-contained user management (username, e-mail, password) with mandatory e-mail verification (double opt-in), password reset, rate limiting, and the option to disable self-registration per instance. SSO is not in the MVP but the design must allow adding OIDC login later without schema surgery. ## Decision ### Authentication - **Server-side sessions** stored in PostgreSQL, referenced by an opaque `HttpOnly; Secure; SameSite=Lax` cookie. No JWTs for browser sessions (revocability and simplicity win). ~~Sliding expiration, default 30 days.~~ _Amended by issue #190 (2026-07-30): a configurable **absolute** bound (`SESSION_ABSOLUTE_HOURS`, default 7 days, never extended by activity) plus a configurable **idle** bound (`SESSION_IDLE_HOURS`, default 3 days, renewed by activity, enforced server-side against `lastSeenAt`). The sliding 30-day expiry is gone._ - Passwords hashed with **Argon2id** (tuned parameters documented in code). - **E-mail flows** (verification, password reset) use single-use, expiring, hashed tokens; mail is sent via SMTP (instance-configured, see setup wizard) through a mail outbox table with retry. - **Rate limiting** on signup, login, password reset, and token endpoints: fixed-window counters in PostgreSQL keyed by IP and by account — no Redis (ADR 0002). - Self-registration can be disabled instance-wide (`instance_settings.registration_mode`: `open` | `closed`). The kickoff also asked for the option of admin approval as a later hardening step; the setting is an enum so `approval_required` can be added without migration pain. - **Collaboration tokens**: the API issues short-lived (≤ 60 s validity for connect) signed JWTs solely for the WebSocket handshake with the collab server (ADR 0003). These are the only JWTs in the system. ### OIDC readiness (not in MVP) - The identity model separates **account** from **login method**: `users` (profile, status) and `user_identities` (`provider` = `password` | future `oidc:`, `subject`, `credential`). Password login is just one identity row. - E-mail is unique per user and verified; future OIDC linking matches on verified e-mail or explicit account linking. ## Consequences - Logout and account deactivation are immediate (session rows deleted). - No shared secret sprawl: one signing key for collab tokens, rotated via environment configuration. - Adding OIDC later means: new identity provider rows, an authorization-code flow module, and a login button — no changes to sessions, permissions, or user references. ## Alternatives considered - **JWT access/refresh tokens in the browser**: harder revocation, XSS exposure of tokens, no benefit for a same-origin SPA. Rejected. - **Auth libraries/services (Keycloak, Authentik as mandatory)**: heavy extra container contradicting easy self-hosting; external IdPs remain possible later through the OIDC path.