Skip to content

Add OIDC IdP auth with per-user conversations + single-user fallback #1270

@rowan-stein

Description

@rowan-stein

User request

Add OIDC IdP support so different users can sign in and have their own conversations. If no IdP is configured, the system should continue to work in a single-user mode (current behavior).

Specification

Approach

Implement OIDC using Authorization Code + PKCE with server-side sessions (secure cookie). All thread/conversation persistence must be scoped by the authenticated principal. In AUTH_MODE=single_user, the server must behave as authenticated as a default user.

Configuration

  • AUTH_MODE=single_user|oidc (default: single_user)

When AUTH_MODE=oidc, require:

  • OIDC_ISSUER_URL
  • OIDC_CLIENT_ID
  • OIDC_CLIENT_SECRET (if confidential client)
  • OIDC_REDIRECT_URI
  • OIDC_SCOPES (default openid profile email)
  • OIDC_POST_LOGIN_REDIRECT (default /)

Session/cookie:

  • SESSION_SECRET
  • cookie flags: HttpOnly, Secure in prod, SameSite (default Lax)

Backend API

Add auth endpoints:

  • GET /api/auth/status -> { mode, authenticated, user }
  • GET /api/auth/login -> starts OIDC redirect (no-op in single_user)
  • GET /api/auth/oidc/callback -> validates state/nonce/PKCE, creates session, redirects to UI
  • POST /api/auth/logout -> clears session

Data model (Prisma)

  • Add User model (UUID id) with optional OIDC fields oidcIssuer, oidcSubject (unique together), plus email, name.
  • Add Thread.ownerUserId (non-null) FK to User.

Migration/backfill:

  1. Add User table.
  2. Create a default user row used in single-user mode.
  3. Add Thread.ownerUserId (initially nullable), backfill existing threads to default user, then make non-null and add indexes.

Authorization / scoping

  • Plumb a principal (includes userId) through HTTP and websocket contexts.
  • All thread list/fetch/create/update operations must include ownerUserId = principal.userId.
  • Websocket connections must require auth when AUTH_MODE=oidc.

UI behavior

  • On startup call /api/auth/status.
  • If mode=oidc and unauthenticated, redirect to /api/auth/login.
  • Provide logout action.
  • If API is cross-origin, set axios to withCredentials: true and configure CORS.

Security requirements

  • OIDC: validate state + nonce and implement PKCE.
  • Session cookies must be HttpOnly and Secure (prod).
  • Add CSRF mitigation for state-changing endpoints if needed beyond SameSite.

Acceptance criteria

  • With AUTH_MODE=single_user, existing UX works and all threads belong to default user.
  • With AUTH_MODE=oidc, user A cannot see or access user B threads via REST or websocket.
  • Successful login creates/updates a local User record and session; logout ends the session.
  • Prisma migration included and existing data is safely backfilled.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions