Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
25 changes: 20 additions & 5 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
107 changes: 107 additions & 0 deletions docs/decisions/0002-authentication-security-policy.md
Original file line number Diff line number Diff line change
@@ -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`
7 changes: 7 additions & 0 deletions docs/requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down