From 66e64bff2bcc28e42c0708abc97595ba29946ccd Mon Sep 17 00:00:00 2001 From: rd-code-23 Date: Mon, 17 Aug 2026 13:57:40 -0700 Subject: [PATCH 1/2] docs: define authentication architecture --- AGENTS.md | 19 +++++++++++++++- docs/architecture.md | 54 ++++++++++++++++++++++++++++++++++++-------- docs/database.md | 50 ++++++++++++++++++++++++++++++++++++++-- docs/requirements.md | 13 +++++++++-- 4 files changed, 122 insertions(+), 14 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 65657c7..1c22408 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -327,6 +327,23 @@ At minimum: - avoid leaking stack traces or internal errors to clients - follow least-privilege principles +For MVP authentication: + +- use Google OIDC for registered-user login +- use server-side sessions stored in PostgreSQL +- place only an opaque, cryptographically random session ID in an `HttpOnly` + cookie +- set `Secure` in production and choose `SameSite`, domain, path, and expiration + settings intentionally +- do not introduce JWT-based application authentication unless the documented + architecture is deliberately changed +- enforce authentication and resource authorization on the server for every + protected request +- invalidate the server-side session on logout and reject expired sessions +- implement CSRF protection appropriate for cookie-based authentication +- use maintained OIDC and session-management libraries rather than implementing + security protocols or session primitives from scratch + Never commit: - passwords @@ -503,4 +520,4 @@ with: Build the simplest production-quality solution that satisfies the current requirements and leaves reasonable room for future evolution. -Production quality does not mean maximum complexity. \ No newline at end of file +Production quality does not mean maximum complexity. diff --git a/docs/architecture.md b/docs/architecture.md index b511728..ef9bbc6 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -646,23 +646,59 @@ Never commit real credentials or secrets. # 27. Authentication -MVP authentication should support: +Registered users authenticate with Google using OpenID Connect (OIDC). -- Google authentication -- guest access +After a successful OIDC flow, the API finds or creates the corresponding +GoForLift user and creates a server-side application session. Session records are +stored in PostgreSQL. The browser receives only a cryptographically random, +opaque session ID in an `HttpOnly` cookie; application authentication does not +use JWTs for the MVP. -The exact authentication implementation/library has not yet been finalized. +The authentication flow is conceptually: -Do not choose or implement an authentication provider solely based on assumption. + Browser + | + | Google OIDC login + v + Express API + | + | find/create user + create session + v + PostgreSQL -Authentication architecture should be finalized when the authentication feature -is implemented. + Browser <- HttpOnly session ID cookie <- Express API + +Use a maintained OIDC and session-management library rather than implementing +OIDC, cookie signing, or session lifecycle behavior from scratch. The exact +library should be selected during implementation based on compatibility, +maintenance, and security posture. + +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 + +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. + +Guest access remains unauthenticated and does not create a persistent session or +registered-user data. ## 27.1 Authorization Authorization must occur on the backend. +Every protected request must resolve a valid server-side session and enforce +resource ownership or other applicable authorization rules. Authentication alone +does not grant access to every registered user's resources. + A user must not be able to access or modify another user's: - routines @@ -917,7 +953,7 @@ requirements. | Structure | Feature-based / vertical | | Architecture | Modular monolith | | Active workout | Client-side until finish/completion | -| Authentication | Google + guest; implementation TBD | +| Authentication | Google OIDC + PostgreSQL-backed server-side sessions; opaque session ID in an HttpOnly cookie; no application JWT for MVP | | Deployment | TBD | | Android | Future client using the same API | @@ -930,4 +966,4 @@ When choosing between two solutions, prefer the one that: 2. is easy to understand, 3. is easy to test, 4. preserves important future options, -5. introduces the least unnecessary complexity. \ No newline at end of file +5. introduces the least unnecessary complexity. diff --git a/docs/database.md b/docs/database.md index 9637577..dafef19 100644 --- a/docs/database.md +++ b/docs/database.md @@ -42,6 +42,8 @@ otherwise make those features unnecessarily difficult. At a high level: User + | + +---- Authentication Sessions | +---- Routines | @@ -99,6 +101,38 @@ Guest users are not required to have persistent database records. than assuming email is always the canonical external identity. +## 4.1 Authentication Sessions + +### Purpose + +Stores server-side sessions for registered users authenticated through Google +OIDC. The browser stores only an opaque, cryptographically random session ID in +an `HttpOnly` cookie. GoForLift does not use JWTs for application authentication +in the MVP. + +### Fields + +| Field | Type | Purpose | +|---|---|---| +| `id` | UUID or sufficiently strong opaque identifier | Session identifier placed in the browser cookie. If the implementation stores only a hash of the cookie value, this field contains that hash instead. | +| `user_id` | UUID, FK → `users.id` | User authenticated by the session. | +| `expires_at` | TIMESTAMPTZ | Absolute time after which the session is invalid. | +| `created_at` | TIMESTAMPTZ | When the session was created. | +| `last_accessed_at` | TIMESTAMPTZ, nullable | Optional timestamp for idle-expiration or session-management behavior if adopted during implementation. | + +### Constraints and Lifecycle + +- `id` is the primary key or uniquely indexed lookup value. +- `user_id` references `users.id`. +- Session identifiers must be generated using a cryptographically secure source. +- Expired sessions must not authenticate requests and should be removed + periodically. +- Logout deletes or invalidates the matching session. +- Deleting a user must invalidate that user's sessions. +- An index on `user_id` supports user-wide session revocation. +- An index on `expires_at` may support expiration cleanup. + + # 5. Exercises ## Purpose @@ -443,6 +477,7 @@ The initial persistent model contains: | Table | Purpose | |---|---| | `users` | Registered user identity. | +| `sessions` | Server-side authentication sessions for registered users. | | `exercises` | Built-in and user-created exercises. | | `routines` | User-created workout routines. | | `routine_exercises` | Exercise configuration within a routine. | @@ -451,13 +486,20 @@ The initial persistent model contains: | `workout_sets` | Sets actually completed during workouts. | | `user_settings` | Persisted UI/user preferences. | -This gives the MVP **8 core tables**. +This gives the MVP **9 core tables**. Do not add a progression table because progression is not part of the MVP. # 15. Relationship Summary + users + | + | 1:N + v + sessions + + users | | 1:N @@ -577,6 +619,10 @@ Likely query patterns should guide additional indexes. Potential candidates include: + sessions.user_id + + sessions.expires_at + routines.user_id exercises.owner_user_id @@ -772,4 +818,4 @@ and: ↓ WORKOUT HISTORY -That distinction is fundamental to the GoForLift data model. \ No newline at end of file +That distinction is fundamental to the GoForLift data model. diff --git a/docs/requirements.md b/docs/requirements.md index 933d8f7..ceba5b3 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -56,7 +56,15 @@ MVP UX only needs to handle straightforward dumbbell workouts well. ## 4.1 Registered Users -Users should be able to authenticate using Google. +Users should be able to authenticate using Google OIDC. + +After authentication, GoForLift should maintain a server-side session stored in +PostgreSQL. The browser should receive only an opaque session ID in an +`HttpOnly` cookie. Application authentication should not use JWTs for the MVP. + +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 users have persistent data. @@ -80,6 +88,7 @@ the application without creating an account. Guest mode: - does not require authentication +- does not create a registered-user authentication session - does not persist user data permanently - should allow meaningful exploration of the application's core functionality @@ -633,4 +642,4 @@ The MVP is successful when a user can reliably: 14. View useful basic workout statistics. 15. Use the application comfortably on mobile and desktop. 16. Switch between light and dark themes. -17. Customize the supported accent color. \ No newline at end of file +17. Customize the supported accent color. From ade8cdbb3f7e20980ddef304b4fdb429ec3b6b61 Mon Sep 17 00:00:00 2001 From: rd-code-23 Date: Mon, 17 Aug 2026 14:33:20 -0700 Subject: [PATCH 2/2] docs(auth): record authentication library decision --- docs/architecture.md | 23 +- .../0001-authentication-libraries.md | 219 ++++++++++++++++++ 2 files changed, 238 insertions(+), 4 deletions(-) create mode 100644 docs/decisions/0001-authentication-libraries.md diff --git a/docs/architecture.md b/docs/architecture.md index ef9bbc6..d9f8673 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -668,10 +668,25 @@ The authentication flow is conceptually: Browser <- HttpOnly session ID cookie <- Express API -Use a maintained OIDC and session-management library rather than implementing -OIDC, cookie signing, or session lifecycle behavior from scratch. The exact -library should be selected during implementation based on compatibility, -maintenance, and security posture. +The MVP authentication stack is: + +- `openid-client` for OIDC discovery and Authorization Code Flow +- `express-session` for session lifecycle and cookie handling +- `connect-pg-simple` for PostgreSQL session persistence using the existing + `pg` connection pool +- `csrf-sync` for synchronizer-token CSRF protection + +Drizzle owns the session-table migration. Runtime table creation through the +session store must remain disabled. + +Do not add Passport around `openid-client` for the MVP. It would introduce an +additional authentication abstraction without solving a current requirement. +Do not retain Google provider tokens after login unless a later feature requires +access to a Google API. + +The full decision, alternatives, and consequences are recorded in: + + docs/decisions/0001-authentication-libraries.md Session cookies must use: diff --git a/docs/decisions/0001-authentication-libraries.md b/docs/decisions/0001-authentication-libraries.md new file mode 100644 index 0000000..bc70e58 --- /dev/null +++ b/docs/decisions/0001-authentication-libraries.md @@ -0,0 +1,219 @@ +# ADR 0001: Authentication Libraries + +## Status + +Accepted + + +## Context + +GoForLift needs Google OpenID Connect login for a React web application backed +by one Express 5 API and PostgreSQL database. + +The documented authentication architecture requires: + +- Google OIDC for registered-user identity +- PostgreSQL-backed server-side sessions +- an opaque session ID in an `HttpOnly` cookie +- CSRF protection for state-changing requests +- no GoForLift-issued JWT authentication for the MVP +- maintained libraries instead of custom protocol or session implementations + +The repository uses ESM, TypeScript, Node.js, Express 5, `pg`, Drizzle, and pnpm. +Selected libraries must fit that stack without introducing unnecessary identity +infrastructure or overlapping abstractions. + + +## Decision Drivers + +- Correct implementation of OIDC validation and Authorization Code Flow +- Active maintenance and a documented security policy +- Express 5, ESM, TypeScript, and current Node.js compatibility +- PostgreSQL session persistence without another infrastructure service +- Explicit session revocation and expiration behavior +- A CSRF design appropriate for stateful cookie authentication +- Minimal conceptual and operational complexity for one web client and one API +- Clear boundaries between protocol handling, session lifecycle, persistence, + and application authorization + + +## Decision + +Use the following libraries for MVP authentication: + +Selected package baselines, verified on August 17, 2026: + +- `openid-client` `^6.8.5` +- `express-session` `^1.19.0` +- `connect-pg-simple` `^10.0.0` +- `csrf-sync` `^4.2.1` + +These are the reviewed starting versions. The lockfile remains authoritative for +the exact installed dependency graph, and dependency updates still require normal +review and automated verification. + +### `openid-client` + +Use `openid-client` for: + +- Google OIDC discovery +- Authorization Code Flow +- PKCE generation and verification +- state and nonce validation +- authorization response and ID-token claim validation + +GoForLift will identify a Google user by the stable `sub` claim. Email is profile +and contact data, not the canonical external identity key. + + +### `express-session` + +Use `express-session` for: + +- session ID generation and cookie handling +- typed server-side session state +- session ID regeneration after successful login +- session destruction during logout + +Configure it with: + +- `resave: false` +- `saveUninitialized: false` +- an explicit production cookie policy +- a production PostgreSQL store rather than the default memory store + +The session contains only the GoForLift state needed for authentication and the +OIDC/CSRF flows. Google access tokens and ID tokens are not retained after login +unless a future Google API integration creates a specific requirement. + + +### `connect-pg-simple` + +Use `connect-pg-simple` as the `express-session` PostgreSQL store. + +It will reuse the API's existing `pg` connection pool. Drizzle owns the session +table migration, so `createTableIfMissing` remains disabled. Expiration and +pruning behavior must match the documented session policy. + + +### `csrf-sync` + +Use `csrf-sync` to implement the synchronizer-token pattern for state-changing +cookie-authenticated requests. + +The CSRF token is stored in the server-side session and returned through a +dedicated API response. The React client sends it in the `X-CSRF-Token` header. +Safe HTTP methods must not perform state changes. + + +## Alternatives Considered + +### Passport with a Google or OIDC strategy + +Rejected for the MVP. + +Passport would add a second authentication abstraction around protocol behavior +already handled by `openid-client`. GoForLift currently has one identity provider +and does not need Passport's strategy ecosystem. + + +### Google's authentication libraries alone + +Rejected as the primary browser OIDC integration. + +Google libraries can verify Google tokens, but GoForLift also needs a complete, +stateful Authorization Code Flow with discovery, PKCE, state, nonce, callback +validation, and application session creation. `openid-client` provides a more +complete OIDC relying-party boundary. + + +### Custom OIDC implementation + +Rejected. + +OIDC and OAuth contain security-sensitive validation and protocol edge cases. +Implementing these directly would increase risk and maintenance cost without +providing product value. + + +### Custom Drizzle session store + +Rejected initially. + +A custom `express-session` store would provide tighter control over the table +shape but would require GoForLift to own store semantics such as `get`, `set`, +`destroy`, `touch`, expiration, concurrency behavior, and error handling. The +maintained PostgreSQL adapter is simpler for the MVP. + + +### Redis session storage + +Deferred. + +GoForLift already requires PostgreSQL, and expected MVP traffic does not justify +another service. The session store can be revisited if measured database load or +deployment requirements make Redis valuable. + + +### Stateless application JWTs + +Rejected for the MVP. + +GoForLift has one browser client and one backend. JWTs would complicate immediate +logout and revocation without solving a current distributed-system requirement. +Google may issue a JWT-formatted ID token during OIDC, but that provider token is +not the GoForLift application session. + + +### Double-submit CSRF protection + +Rejected for the current stateful architecture. + +The synchronizer-token pattern naturally uses the server-side session already +required by the application. A double-submit design would be more appropriate +for an otherwise stateless system. + + +## Consequences + +### Positive + +- Security-sensitive OIDC behavior is delegated to a focused, maintained client. +- Logout and revocation remain straightforward server-side operations. +- Sessions reuse PostgreSQL and the existing connection pool. +- Each library has one clear responsibility. +- The architecture remains simple enough for the MVP and explainable in review. + + +### Negative + +- Authenticated requests require a PostgreSQL session lookup. +- The session table must be migrated, indexed, pruned, and monitored. +- Cookie authentication requires intentional CSRF and CORS configuration. +- `connect-pg-simple` uses its own compatible session record shape rather than a + domain-specific normalized session model. +- A future native or third-party client may require a different token strategy. + + +## Follow-up Decisions + +Before session middleware is implemented, finalize: + +- local and production frontend/API origins +- cookie `SameSite`, `Secure`, domain, path, and name +- absolute session lifetime, idle timeout, and renewal behavior +- trusted proxy configuration +- credentialed CORS allowlist +- safe post-login redirect rules +- expired-session pruning policy + +These decisions are tracked separately because they depend on the deployment +topology rather than the library selection itself. + + +## References + +- GitHub issue #3: Select and document authentication libraries +- `docs/architecture.md` +- `docs/database.md` +- `docs/requirements.md`