- Add SSO session cleanup task (10-min expiry, 60s purge interval) - Change callback to redirect to frontend with tokens as query params - Add sso_callback_url to SecurityConfig with serde default - Add SsoCallbackPage.tsx for handling SSO callback redirects - Add /auth/sso/callback public route to App.tsx - Add Sign in with Microsoft Azure button to LoginPage - Replace insecure decode_jwt_payload with verify_id_token - Implement JWKS caching (1-hour TTL) and RSA signature verification - Validate iss, aud, exp claims on id_token - Add jsonwebtoken dependency to pm-web crate - Update config.example.toml with sso_callback_url setting - Add sso_callback_url to settings response (read-only from TOML)
3.0 KiB
3.0 KiB
SSO Implementation Fix Plan
Issues Identified
- No SSO Login Button — LoginPage.tsx missing "Sign in with Azure" button
- No SSO Callback Route — App.tsx missing frontend route to handle SSO callback
- authStore No SSO Support — authStore.ts has no method to store SSO tokens
- Backend Returns JSON Not Redirect — azure_sso.rs callback returns JSON tokens instead of redirecting to frontend
- No SSO Session Cleanup — sso_sessions DashMap has no expiry/cleanup task (memory leak)
- No JWT Signature Verification — id_token decoded without verifying Azure AD signature
Phases
Phase 1: Backend SSO Fixes (Issues 4, 5) — COMPLETE ✅
- 1a: Add SSO session cleanup task in main.rs (purge sessions older than 10 minutes)
- 1b: Modify azure_sso.rs callback to redirect to frontend with tokens instead of returning JSON
- 1c: Add
sso_callback_urlto SecurityConfig in config.rs with serde default - 1d: Update settings.rs to include sso_callback_url in settings response
- 1e: Verify backend compiles with
cargo check
Phase 2: Frontend SSO Integration (Issues 1, 2, 3) — COMPLETE ✅
- 2a: Add SSO callback page component (SsoCallbackPage.tsx)
- 2b: Add SSO callback route to App.tsx (public route, no auth required)
- 2c: Add "Sign in with Microsoft Azure" button to LoginPage.tsx
- 2d: Add SSO-related types and API methods to frontend
- 2e: Verify frontend builds with TypeScript compilation
Phase 3: JWT Signature Verification (Issue 6) — COMPLETE ✅
- 3a: Add JWKS client dependency to pm-web/Cargo.toml
- 3b: Implement id_token signature verification in azure_sso.rs
- 3c: Verify backend compiles with
cargo check
Phase 4: Integration Testing and Verification — COMPLETE ✅
- 4a: Backend code review — all changes verified manually
- 4b: Frontend TypeScript compilation — passes cleanly
- 4c: SSO login flow reviewed end-to-end (backend redirect → frontend callback → auth store)
- 4d: SSO session cleanup verified (10-minute expiry, 60-second purge interval)
- 4e: Settings page SSO config unchanged (sso_callback_url added as read-only)
- 4f: Lessons captured below
Lessons Learned
- SSO callback must redirect, not return JSON — Browser OAuth2 flows require the backend to redirect to the frontend SPA, not return JSON tokens. The frontend must parse tokens from URL query parameters.
- URLSearchParams.get() already decodes — Don't double-decode with decodeURIComponent() when using URLSearchParams.
- JWKS caching prevents rate-limiting — Azure AD JWKS endpoint should be cached with TTL (1 hour) to avoid fetching on every SSO login.
- tokio::sync::Mutex over std::sync::Mutex — Axum handlers must be Send; std::sync::MutexGuard is not Send across await points.
- DashMap session cleanup — In-memory session stores (DashMap) need periodic cleanup tasks to prevent memory leaks. Pattern: tokio::spawn with interval + retain with time-based cutoff.