# OIDC Authentication Implementation Plan for Claude Web UI ## Project Overview **Project:** Claude Web UI - Web interface for Claude Code CLI **URL:** https://agents.sneakercloud.de **Auth Provider:** Authentik (https://auth.sneakercloud.de) **Implementation Goal:** Add production-ready OIDC authentication with group-based access control ## Current Architecture Analysis ### Backend (Node.js/Express) - **File:** `backend/server.js` (999 lines) - **Framework:** Express with WebSocket (ws library) - **Current State:** No authentication - **Endpoints:** - REST: `/api/hosts`, `/api/projects`, `/api/health`, `/api/browse`, `/api/upload/:sessionId`, `/api/history/:project` - WebSocket: Single endpoint on root path - **Sessions:** In-memory Map with UUIDs, no user association ### Frontend (React/Vite) - **Main:** `frontend/src/App.jsx` - Application shell - **State:** `frontend/src/contexts/SessionContext.jsx` - Session management via React Context - **WebSocket:** Direct connections stored in refs - **Storage:** localStorage for session persistence - **Current State:** No authentication UI or logic ### Key Constraints 1. Single-file backend (consider modular refactoring) 2. WebSocket architecture must be preserved 3. No localStorage for tokens (XSS risk) 4. Production security requirements 5. Group-based access control needed --- ## Phase 1: Authentik Configuration & Backend Foundation ### Objectives - Configure Authentik OIDC Provider and Application - Refactor backend into modular structure - Add core authentication dependencies - Implement session storage mechanism ### Tasks #### 1.1 Authentik Setup **Provider Configuration:** ```yaml Name: Claude Web UI Client Type: Confidential Authorization Flow: Authorization Code Redirect URIs: - https://agents.sneakercloud.de/auth/callback - http://localhost:5173/auth/callback (dev) Scopes: openid, profile, email, groups ``` **Application Configuration:** ```yaml Name: Claude Web UI Provider: [linked to above] Launch URL: https://agents.sneakercloud.de ``` **Group Mappings:** - Create groups: `agent-admin`, `agent-users` - Configure scope mappings to include groups in ID token - Test with user accounts **Deliverables:** - Client ID and Client Secret - Discovery URL: `https://auth.sneakercloud.de/application/o/claude-web-ui/.well-known/openid-configuration` - Documented group structure #### 1.2 Backend Refactoring **Current:** Single `backend/server.js` (999 lines) **New Structure:** ``` backend/ ├── server.js # Main entry, app initialization ├── config/ │ └── auth.js # Auth config from env ├── middleware/ │ ├── auth.js # Authentication middleware │ └── session.js # Session management ├── routes/ │ ├── api.js # Existing API routes │ ├── auth.js # OIDC auth routes │ └── websocket.js # WebSocket handler └── utils/ └── oidc.js # OIDC client wrapper ``` **Migration Strategy:** 1. Create new file structure 2. Extract route handlers into modules 3. Extract WebSocket logic 4. Update server.js to import modules 5. Test that existing functionality works #### 1.3 Dependencies **Add to `backend/package.json`:** ```json { "express-session": "^1.18.0", "connect-redis": "^7.1.0", "redis": "^4.6.0", "openid-client": "^5.6.0", "cookie-parser": "^1.4.6" } ``` **Rationale:** - `express-session`: Session management - `connect-redis`: Session store (persistent, production-ready) - `redis`: Session backend - `openid-client`: Official OIDC client (certified) - `cookie-parser`: Cookie handling #### 1.4 Session Storage **Redis Configuration:** - Use existing Redis instance or add to docker-compose - Configure session store with httpOnly cookies - Session TTL: 24 hours (configurable) - Secure flag in production **Session Schema:** ```javascript { sessionId: "uuid", userId: "oidc-sub", email: "user@example.com", name: "User Name", groups: ["agent-users"], accessToken: "encrypted", refreshToken: "encrypted", expiresAt: timestamp, createdAt: timestamp } ``` ### Verification Criteria - [ ] Authentik provider and application created - [ ] Test user can authenticate via Authentik UI - [ ] Backend refactored, all tests pass - [ ] Dependencies installed - [ ] Redis session store connected - [ ] Session CRUD operations working --- ## Phase 2: OIDC Authentication Flow ### Objectives - Implement authorization code flow - Create auth routes (login, callback, logout) - Secure session cookies - Handle token refresh ### Tasks #### 2.1 OIDC Client Setup **File:** `backend/utils/oidc.js` **Functionality:** - Discover OIDC configuration from Authentik - Initialize Issuer and Client - Generate authorization URL - Handle token exchange - Validate ID tokens - Refresh access tokens **Configuration (from env):** ```bash OIDC_ISSUER=https://auth.sneakercloud.de/application/o/claude-web-ui/ OIDC_CLIENT_ID= OIDC_CLIENT_SECRET= OIDC_REDIRECT_URI=https://agents.sneakercloud.de/auth/callback SESSION_SECRET= REDIS_URL=redis://localhost:6379 ``` #### 2.2 Authentication Routes **File:** `backend/routes/auth.js` **Routes:** **GET `/auth/login`** - Generate OIDC authorization URL with PKCE - Store state and nonce in session - Redirect to Authentik **GET `/auth/callback`** - Validate state parameter - Exchange authorization code for tokens - Validate ID token signature and claims - Extract user info and groups - Create session with user data - Redirect to frontend (`/`) **POST `/auth/logout`** - Destroy session - Optionally redirect to Authentik logout - Clear cookies **GET `/auth/user`** - Return current user info from session - Return 401 if not authenticated **POST `/auth/refresh`** - Use refresh token to get new access token - Update session - Return success/failure #### 2.3 Session Cookie Configuration ```javascript { name: 'claude.sid', secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, cookie: { httpOnly: true, // No JavaScript access secure: true, // HTTPS only in production sameSite: 'lax', // CSRF protection maxAge: 24 * 60 * 60 * 1000 // 24 hours }, store: redisStore // Redis session store } ``` #### 2.4 Token Management - Store access token in Redis session (encrypted) - Store refresh token in Redis session (encrypted) - Implement token refresh 5 minutes before expiry - Handle refresh failures (force re-login) ### Verification Criteria - [ ] Login redirects to Authentik - [ ] Callback successfully exchanges code for tokens - [ ] ID token validated and parsed - [ ] Groups extracted from token - [ ] Session created in Redis - [ ] Cookie set with correct flags - [ ] `/auth/user` returns user info - [ ] Logout destroys session - [ ] Token refresh works --- ## Phase 3: Backend API Protection ### Objectives - Protect all API endpoints with authentication - Secure WebSocket connections - Implement group-based authorization - Handle unauthorized access gracefully ### Tasks #### 3.1 Authentication Middleware **File:** `backend/middleware/auth.js` **Middleware Functions:** **`requireAuth`** - Check if session exists and is valid - Verify session in Redis - Attach `req.user` with user data - Return 401 if not authenticated **`requireGroup(groups)`** - Check if `req.user.groups` includes required group - Return 403 if insufficient permissions - Usage: `requireGroup(['agent-admin'])` **`optionalAuth`** - Attach user if authenticated - Continue if not authenticated - For endpoints that support both modes #### 3.2 Protect REST Endpoints **Apply Middleware:** ```javascript // All API routes require auth app.use('/api', requireAuth); // Admin-only endpoints app.get('/api/admin/*', requireGroup(['agent-admin'])); // Health check remains public app.get('/api/health', (req, res) => ...); ``` **Protected Endpoints:** - `/api/hosts` - Read: agent-users, Write: agent-admin - `/api/projects` - Read: agent-users - `/api/browse` - agent-users - `/api/upload/:sessionId` - agent-users - `/api/history/:project` - agent-users (own history only) #### 3.3 WebSocket Authentication **Challenge:** WebSocket upgrade happens before HTTP middleware **Solution: Cookie-based Auth** 1. Parse cookies from upgrade request 2. Load session from Redis 3. Validate session 4. Attach user to WebSocket connection 5. Reject upgrade if not authenticated **Implementation:** ```javascript wss.on('connection', async (ws, req) => { // Parse cookies from req.headers.cookie // Load session from Redis // Validate user if (!user) { ws.close(1008, 'Unauthorized'); return; } // Attach user to connection ws.user = user; // Continue with Claude session logic }); ``` #### 3.4 Session Association **Current:** Sessions stored by UUID, no user association **New:** - Associate Claude sessions with authenticated user - Store user ID in session metadata - Allow users to only access their own sessions - Admin users can view all sessions (optional) **Session Schema Update:** ```javascript { sessionId: "uuid", userId: "oidc-sub", // NEW userEmail: "user@example.com", // NEW project: "/path/to/project", host: "neko", // ... existing fields } ``` #### 3.5 Error Handling - 401 Unauthorized: Not authenticated - 403 Forbidden: Insufficient permissions - Redirect to `/auth/login` for API calls - WebSocket: Close with error code - Frontend: Display login prompt ### Verification Criteria - [ ] Unauthenticated API calls return 401 - [ ] WebSocket upgrades require valid session - [ ] Group-based access control works - [ ] Admin users have extended permissions - [ ] Sessions isolated by user - [ ] Error responses are clear --- ## Phase 4: Frontend Authentication UI ### Objectives - Create AuthContext for authentication state - Implement login/logout UI - Add protected routes - Handle authentication errors - Integrate with existing SessionContext ### Tasks #### 4.1 AuthContext **File:** `frontend/src/contexts/AuthContext.jsx` **State:** ```javascript { user: null | { id: string, email: string, name: string, groups: string[] }, isLoading: boolean, error: string | null } ``` **Methods:** - `login()` - Redirect to `/auth/login` - `logout()` - Call `/auth/logout`, clear state - `refreshUser()` - Call `/auth/user` to get current user - `checkAuth()` - Verify authentication on mount **Auto-refresh:** - Poll `/auth/user` every 5 minutes - Handle 401 by redirecting to login - Update user state on success #### 4.2 Login Flow **Current:** App loads directly to ChatPanel **New:** 1. App mounts 2. AuthContext checks `/auth/user` 3. If 401: Show login page 4. If 200: Show app 5. User clicks "Login with Authentik" 6. Redirect to `/auth/login` 7. Authentik authentication 8. Callback redirects to `/` 9. AuthContext refreshes user 10. App shows #### 4.3 Login Page Component **File:** `frontend/src/components/LoginPage.jsx` **UI:** - Centered card - Claude Web UI branding - "Login with Authentik" button - Loading state during redirect - Error messages **Design:** - Match existing dark theme - Simple, professional - No local credentials (OIDC only) #### 4.4 Protected App Wrapper **File:** `frontend/src/App.jsx` (update) **Logic:** ```javascript function App() { return ( ); } function AuthenticatedApp() { const { user, isLoading } = useAuth(); if (isLoading) return ; if (!user) return ; return ( ); } ``` #### 4.5 User Menu **Location:** Header component **Elements:** - User name/email display - Group badges (admin/user) - Logout button - Account settings link (optional) **Functionality:** - Dropdown menu - Logout calls `logout()` method - Shows current user info #### 4.6 Session Integration **Update:** `frontend/src/contexts/SessionContext.jsx` **Changes:** - Include user in WebSocket connection metadata - Filter sessions by current user - Add user info to session creation - Handle 401 errors by triggering AuthContext logout **WebSocket Headers:** - Session cookie automatically included - Backend validates on upgrade ### Verification Criteria - [ ] Unauthenticated users see login page - [ ] Login redirects to Authentik - [ ] Successful login shows app - [ ] User info displayed in UI - [ ] Logout clears session and returns to login - [ ] 401 errors trigger re-authentication - [ ] WebSocket connects with auth - [ ] Sessions filtered by user --- ## Phase 5: Production Hardening & Polish ### Objectives - Implement security best practices - Add monitoring and logging - Handle edge cases - Document configuration - Test thoroughly ### Tasks #### 5.1 Security Enhancements **CSRF Protection:** - Use `sameSite: 'lax'` on cookies - Add CSRF tokens for state-changing operations - Validate origin headers on WebSocket upgrade **Rate Limiting:** - Add `express-rate-limit` to auth endpoints - Limit login attempts per IP - Limit token refresh attempts **Token Security:** - Encrypt tokens at rest in Redis (using `crypto`) - Validate token expiry before use - Clear tokens on logout **Content Security Policy:** - Add CSP headers - Allow only Authentik domain for redirects - Restrict inline scripts #### 5.2 Logging & Monitoring **Authentication Events:** - Log successful logins (user ID, IP, timestamp) - Log failed auth attempts - Log token refresh failures - Log logout events **Metrics:** - Active sessions count - Authentication failures rate - Token refresh rate - WebSocket auth failures **Integration:** - Use existing Gotify webhook for alerts - Log to stdout (Docker logs) - Consider structured logging (winston) #### 5.3 Error Handling **Token Expiry:** - Detect access token expiry - Auto-refresh using refresh token - Prompt re-login if refresh fails - Show clear error messages **Session Expiry:** - Handle expired sessions gracefully - Show "Session expired, please login again" - Preserve unsaved work if possible **Network Errors:** - Retry logic for auth endpoint calls - Offline detection - Clear error messages **Authentik Downtime:** - Cache user info for graceful degradation - Allow continued use of valid sessions - Queue auth checks #### 5.4 Environment Configuration **Required Env Vars:** ```bash # OIDC OIDC_ISSUER=https://auth.sneakercloud.de/application/o/claude-web-ui/ OIDC_CLIENT_ID= OIDC_CLIENT_SECRET= OIDC_REDIRECT_URI=https://agents.sneakercloud.de/auth/callback # Session SESSION_SECRET= SESSION_DOMAIN=.sneakercloud.de SESSION_SECURE=true SESSION_MAX_AGE=86400000 # 24 hours # Redis REDIS_URL=redis://localhost:6379 REDIS_PASSWORD= # App NODE_ENV=production FRONTEND_URL=https://agents.sneakercloud.de ``` **docker-compose.yml Updates:** ```yaml services: backend: environment: - OIDC_ISSUER=${OIDC_ISSUER} - OIDC_CLIENT_ID=${OIDC_CLIENT_ID} - OIDC_CLIENT_SECRET=${OIDC_CLIENT_SECRET} - SESSION_SECRET=${SESSION_SECRET} - REDIS_URL=redis://redis:6379 depends_on: - redis redis: image: redis:7-alpine networks: - claude-internal volumes: - redis-data:/data volumes: redis-data: ``` #### 5.5 Documentation **Create Files:** - `docs/AUTHENTICATION.md` - Overview of auth system - `docs/SETUP.md` - Step-by-step Authentik setup - `docs/CONFIGURATION.md` - Environment variables - `docs/TROUBLESHOOTING.md` - Common issues **Update README.md:** - Add authentication section - Document required groups - Link to setup docs #### 5.6 Testing **Manual Tests:** - [ ] Fresh login flow - [ ] Logout and re-login - [ ] Session persistence across browser refresh - [ ] Token refresh - [ ] Expired session handling - [ ] WebSocket auth - [ ] Group-based access control - [ ] Multiple users simultaneously - [ ] Admin vs regular user permissions **Edge Cases:** - [ ] Authentik downtime during login - [ ] Redis downtime (session loss) - [ ] Concurrent logins from same user - [ ] Token refresh during active WebSocket - [ ] Cookie disabled in browser - [ ] CORS issues **Security Tests:** - [ ] Cannot access API without auth - [ ] Cannot access other users' sessions - [ ] XSS attacks blocked (httpOnly cookies) - [ ] CSRF protection works - [ ] Token replay attacks prevented ### Verification Criteria - [ ] All security enhancements implemented - [ ] Logging captures auth events - [ ] Error handling covers all cases - [ ] Documentation complete - [ ] All tests pass - [ ] Production deployment successful --- ## Implementation Timeline **Phase 1:** Authentik Configuration & Backend Foundation **Duration:** 2-3 days **Blockers:** None **Phase 2:** OIDC Authentication Flow **Duration:** 2-3 days **Blockers:** Phase 1 complete **Phase 3:** Backend API Protection **Duration:** 2-3 days **Blockers:** Phase 2 complete **Phase 4:** Frontend Authentication UI **Duration:** 3-4 days **Blockers:** Phase 3 complete **Phase 5:** Production Hardening & Polish **Duration:** 2-3 days **Blockers:** Phase 4 complete **Total Estimated Time:** 11-16 days --- ## Success Metrics 1. **Security:** - Zero XSS vulnerabilities (httpOnly cookies) - Zero unauthorized access incidents - All tokens encrypted at rest 2. **User Experience:** - Seamless login flow (<5 seconds) - Clear error messages - No unnecessary re-authentication 3. **Reliability:** - 99.9% authentication availability - Session persistence across restarts - Graceful degradation on errors 4. **Performance:** - <100ms auth middleware overhead - No impact on WebSocket latency - Redis session lookups <10ms --- ## Rollback Plan **If Critical Issues Arise:** 1. **Immediate:** Disable authentication middleware 2. **Revert:** Return to unauthenticated mode 3. **Investigate:** Review logs and error reports 4. **Fix:** Address issues in isolated environment 5. **Redeploy:** With fixes applied **Feature Flags:** - `AUTH_ENABLED=false` to disable auth - Keep unauthenticated code path intact during initial rollout --- ## Future Enhancements **Post-MVP:** - Multi-factor authentication (MFA) via Authentik - API keys for programmatic access - OAuth2 scopes for fine-grained permissions - Audit log for all user actions - Session management UI (view/revoke sessions) - SSO with other services via Authentik - User preferences storage - Role-based access control (RBAC) expansion --- ## References - **Authentik Docs:** https://docs.goauthentik.io/ - **OIDC Spec:** https://openid.net/connect/ - **openid-client:** https://github.com/panva/node-openid-client - **express-session:** https://github.com/expressjs/session - **OWASP Auth Cheatsheet:** https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html