Files
phaseflow/specs/authentication.md
Petru Paler 2ae6804cc4
All checks were successful
Deploy / deploy (push) Successful in 1m37s
Fix OAuth login by syncing auth state to cookie
Root cause: PocketBase SDK stores auth in localStorage, but Next.js
middleware checks for pb_auth cookie. The cookie was never being set
after successful OAuth login.

Fix: Add pb.authStore.onChange() listener that syncs auth state to
cookie on any change (login, logout, token refresh). This is the
idiomatic PocketBase pattern for Next.js SSR apps.

Also updates authentication spec to reflect that the cookie is
non-HttpOnly by design (client SDK needs read/write access).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 13:59:53 +00:00

4.5 KiB

Authentication Specification

Job to Be Done

When I access PhaseFlow, I want to securely log in with my identity provider, so that my personal health data remains private.

Auth Provider

Using PocketBase for authentication and data storage, with OIDC (Pocket-ID) as the primary identity provider.

Connection:

  • NEXT_PUBLIC_POCKETBASE_URL environment variable
  • src/lib/pocketbase.ts initializes client

Login Flow

OIDC Authentication (Pocket-ID)

  1. User clicks "Sign In" on /login
  2. App redirects to Pocket-ID authorization endpoint
  3. User authenticates with Pocket-ID (handles MFA if configured)
  4. Pocket-ID redirects back with authorization code
  5. PocketBase exchanges code for tokens
  6. User redirected to dashboard

OIDC Configuration

Configure in PocketBase Admin UI: Settings → Auth providers → OpenID Connect

Required Settings:

  • Client ID - From Pocket-ID application
  • Client Secret - From Pocket-ID application
  • Issuer URL - Your Pocket-ID instance URL (e.g., https://id.yourdomain.com)

Environment Variables:

POCKETBASE_OIDC_CLIENT_ID=phaseflow
POCKETBASE_OIDC_CLIENT_SECRET=xxx
POCKETBASE_OIDC_ISSUER_URL=https://id.yourdomain.com

Session Management

  • PocketBase SDK stores auth in localStorage by default
  • pb.authStore.onChange() listener syncs auth state to pb_auth cookie
  • Cookie enables Next.js middleware to check auth server-side
  • Cookie is non-HttpOnly (PocketBase SDK requires client-side access)
  • Session expires after 14 days of inactivity

Pages

/login

Elements:

  • "Sign In with Pocket-ID" button
  • Error message display

Behavior:

  • Redirect to / on successful login
  • Show error message on failed attempt
  • Rate limit: 5 attempts per minute

Protected Routes

All routes except /login require authentication.

Middleware Check:

  1. Check for valid PocketBase auth token
  2. If invalid/missing, redirect to /login
  3. If valid, proceed to requested page

API Authentication

User Context

API routes access current user via:

const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL);
// Auth token from request cookies
const user = pb.authStore.model;

Protected Endpoints

All /api/* routes except:

  • /api/calendar/[userId]/[token].ics (token-based auth)
  • /api/cron/* (CRON_SECRET auth)

User API

GET /api/user

Returns current authenticated user profile.

Response:

{
  "id": "user123",
  "email": "user@example.com",
  "garminConnected": true,
  "cycleLength": 31,
  "lastPeriodDate": "2024-01-01",
  "notificationTime": "07:00",
  "timezone": "America/New_York"
}

PATCH /api/user

Updates user profile fields.

Request Body (partial update):

{
  "cycleLength": 28,
  "notificationTime": "06:30"
}

User Schema

See src/types/index.ts for full User interface.

Auth-related fields:

  • id - PocketBase record ID
  • email - Login email

Profile fields:

  • cycleLength - Personal cycle length (days)
  • notificationTime - Preferred notification hour
  • timezone - User's timezone

PocketBase Client (src/lib/pocketbase.ts)

Exports:

  • pb - Initialized PocketBase client
  • getCurrentUser() - Get authenticated user
  • isAuthenticated() - Check auth status

Settings Page (/settings)

User profile management:

  • View/edit cycle length
  • View/edit notification time
  • View/edit timezone
  • Link to Garmin settings

Success Criteria

  1. OIDC login flow completes in under 5 seconds (including redirect)
  2. Session persists across browser refreshes
  3. Unauthorized access redirects to login
  4. User data isolated by authentication

Acceptance Tests

  • OIDC redirect initiates correctly
  • Successful OIDC callback creates/updates user
  • Session persists after page refresh
  • Protected routes redirect when not authenticated
  • GET /api/user returns current user data
  • PATCH /api/user updates user record
  • Logout clears session completely
  • Auth cookie syncs via onChange listener (non-HttpOnly per PocketBase design)

Future Enhancements

Open Registration

When open registration is enabled:

  • Add /signup page with OIDC provider selection
  • New users created automatically on first OIDC login
  • Admin approval workflow (optional)

Additional OAuth2 Providers

PocketBase supports multiple OAuth2 providers. Future options:

  • Google
  • GitHub
  • Apple
  • Other OIDC-compliant providers

Each provider configured separately in PocketBase Admin UI.