From 12ce82055375dc98634f8a6640ad59b8f23ff12a Mon Sep 17 00:00:00 2001 From: rd-code-23 Date: Mon, 17 Aug 2026 14:58:35 -0700 Subject: [PATCH] docs(auth): define authentication security policy --- AGENTS.md | 2 + docs/architecture.md | 25 +++- .../0002-authentication-security-policy.md | 107 ++++++++++++++++++ docs/requirements.md | 7 ++ 4 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 docs/decisions/0002-authentication-security-policy.md diff --git a/AGENTS.md b/AGENTS.md index 1c22408..e3de78d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -341,6 +341,8 @@ For MVP authentication: protected request - invalidate the server-side session on logout and reject expired sessions - implement CSRF protection appropriate for cookie-based authentication +- follow `docs/decisions/0002-authentication-security-policy.md` for the exact + session lifetime, cookie, CORS, CSRF, proxy, and redirect policy - use maintained OIDC and session-management libraries rather than implementing security protocols or session primitives from scratch diff --git a/docs/architecture.md b/docs/architecture.md index d9f8673..b8376fd 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -692,15 +692,30 @@ Session cookies must use: - `HttpOnly` - `Secure` in production -- an intentionally selected `SameSite` policy compatible with the deployment - topology and OIDC redirect flow -- a narrow path and domain scope where practical -- a defined expiration consistent with the server-side session +- `SameSite=Lax` +- the name `goforlift.sid` +- path `/` with no `Domain` attribute, producing a host-only cookie +- a fixed seven-day expiration with no rolling renewal or idle timeout Logout invalidates the server-side session and clears the cookie. Expired and invalid session IDs must not authenticate a request. Because authentication uses cookies, the implementation must include appropriate CSRF protection for -state-changing requests. +every cookie-authenticated `POST`, `PUT`, `PATCH`, and `DELETE` request. The +browser obtains a synchronizer token tied to the session and returns it in the +`X-CSRF-Token` header. + +Credentialed CORS permits only the explicitly configured web origin and never +combines credentials with a wildcard origin. Local development uses +`http://localhost:5173` for the web and `http://localhost:3000` for the API. +Production origins must be explicit HTTPS URLs. Proxy trust is disabled locally +and narrowly configured for the selected production reverse proxy. + +Post-login destinations must be validated root-relative application paths. +Invalid, absolute, or protocol-relative destinations fall back to `/`. + +The complete policy and its deployment constraints are recorded in: + + docs/decisions/0002-authentication-security-policy.md Guest access remains unauthenticated and does not create a persistent session or registered-user data. diff --git a/docs/decisions/0002-authentication-security-policy.md b/docs/decisions/0002-authentication-security-policy.md new file mode 100644 index 0000000..3a61b28 --- /dev/null +++ b/docs/decisions/0002-authentication-security-policy.md @@ -0,0 +1,107 @@ +# ADR 0002: Authentication Security Policy + +## Status + +Accepted + +## Context + +GoForLift uses Google OIDC and PostgreSQL-backed server-side sessions. The +security-sensitive session, cookie, CORS, CSRF, proxy, and redirect behavior +must be explicit before authentication middleware is implemented. + +The production hosting platform and final hostnames have not yet been selected. +Production origins therefore come from validated configuration rather than +being hard-coded. + +## Decision + +### Origins and transport + +- Local web origin: `http://localhost:5173`. +- Local API origin: `http://localhost:3000`. +- Production web and API origins must be explicit HTTPS URLs supplied through + validated environment configuration. +- A deployment should keep the web and API on the same site when practical. + +### Session lifetime + +- Registered-user sessions have a fixed seven-day absolute lifetime. +- The MVP has no idle timeout and does not use rolling renewal. +- Activity must not extend the absolute expiration. +- Expired sessions fail closed and are pruned from PostgreSQL at least daily. +- Successful login regenerates the session ID; logout destroys the server-side + session and clears the cookie. + +### Session cookie + +The cookie is named `goforlift.sid` and contains only an opaque session ID. + +| Setting | Local development | Production | +| --- | --- | --- | +| `HttpOnly` | `true` | `true` | +| `Secure` | `false` | `true` | +| `SameSite` | `Lax` | `Lax` | +| `Path` | `/` | `/` | +| `Domain` | omitted (host-only) | omitted (host-only) | +| `Max-Age` | seven days | seven days | + +`SameSite=Lax` permits the top-level navigation back from Google while reducing +cross-site cookie sending. A future cross-site web/API deployment must trigger a +new review; it must not silently weaken this policy to `SameSite=None`. + +### Trusted proxy + +- Local development does not trust a proxy. +- Production enables Express `trust proxy` only when deployed behind the known + HTTPS-terminating reverse proxy, using the narrowest setting supported by the + selected platform. +- The final value is deployment configuration and must never default to trusting + arbitrary forwarding headers. + +### Credentialed CORS + +- The API allowlist contains only the configured web origin. +- Credentials are enabled so the browser can send the session cookie. +- A wildcard origin is never combined with credentials. +- Requests with missing or unapproved origins are rejected where CORS applies. +- Allowed methods and headers are limited to those used by the API, including + `X-CSRF-Token` for protected requests. + +### CSRF protection + +GoForLift uses a synchronizer token stored in the server-side session and sent +to the browser through a dedicated endpoint. The browser returns it in the +`X-CSRF-Token` header. + +CSRF validation is required for every cookie-authenticated `POST`, `PUT`, +`PATCH`, and `DELETE` request, including logout. Safe read-only methods do not +require the token. The Google callback is protected by OIDC state, nonce, and +PKCE validation; it does not rely on the synchronizer token. + +Missing, invalid, or expired tokens fail closed. Tokens and session contents +must never be logged. + +### Post-login redirects + +- The client may supply only a root-relative application path beginning with a + single `/`. +- Absolute URLs, protocol-relative values such as `//example.com`, backslashes, + encoded bypasses, and non-HTTP navigation schemes are rejected. +- Invalid or missing destinations fall back to `/`. +- The validated destination is protected by the OIDC flow state and revalidated + before redirecting after login. + +## Consequences + +- Authentication behavior is deterministic across environments. +- Sessions end after seven days even when actively used; renewal can be added + later as a separate product and security decision. +- A hosting topology that requires cross-site cookies needs an explicit policy + review. +- Production configuration must provide exact HTTPS origins and proxy behavior. + +## References + +- GitHub issue #4: Finalize session, cookie, CORS, and CSRF policy +- `docs/decisions/0001-authentication-libraries.md` diff --git a/docs/requirements.md b/docs/requirements.md index ceba5b3..01bfb27 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -66,6 +66,13 @@ Logging out should invalidate the server-side session. Protected resources must require a valid session, and the backend must verify that the authenticated user is authorized to access or modify each resource. +Registered-user sessions should expire after a fixed seven-day lifetime without +rolling renewal. Session cookies must be host-only, `HttpOnly`, `SameSite=Lax`, +and `Secure` in production. Every state-changing cookie-authenticated request +must require a valid session-bound CSRF token. Credentialed CORS must allow only +the configured web origin, and post-login redirects must remain within the web +application. + Registered users have persistent data. Their: