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>
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_URLenvironment variablesrc/lib/pocketbase.tsinitializes client
Login Flow
OIDC Authentication (Pocket-ID)
- User clicks "Sign In" on
/login - App redirects to Pocket-ID authorization endpoint
- User authenticates with Pocket-ID (handles MFA if configured)
- Pocket-ID redirects back with authorization code
- PocketBase exchanges code for tokens
- User redirected to dashboard
OIDC Configuration
Configure in PocketBase Admin UI: Settings → Auth providers → OpenID Connect
Required Settings:
Client ID- From Pocket-ID applicationClient Secret- From Pocket-ID applicationIssuer 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 topb_authcookie- 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:
- Check for valid PocketBase auth token
- If invalid/missing, redirect to
/login - 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 IDemail- Login email
Profile fields:
cycleLength- Personal cycle length (days)notificationTime- Preferred notification hourtimezone- User's timezone
PocketBase Client (src/lib/pocketbase.ts)
Exports:
pb- Initialized PocketBase clientgetCurrentUser()- Get authenticated userisAuthenticated()- 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
- OIDC login flow completes in under 5 seconds (including redirect)
- Session persists across browser refreshes
- Unauthorized access redirects to login
- 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/userreturns current user data - PATCH
/api/userupdates 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
/signuppage 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:
- GitHub
- Apple
- Other OIDC-compliant providers
Each provider configured separately in PocketBase Admin UI.