- Add OIDC login flow with Authentik provider - Implement session-based auth with Redis store - Add avatar display from OIDC claims - Fix input field performance with react-textarea-autosize - Stabilize callbacks to prevent unnecessary re-renders - Fix history loading to skip empty session files - Add 2-row default height for input textarea 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
19 KiB
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
- REST:
- 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
- Single-file backend (consider modular refactoring)
- WebSocket architecture must be preserved
- No localStorage for tokens (XSS risk)
- Production security requirements
- 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:
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:
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:
- Create new file structure
- Extract route handlers into modules
- Extract WebSocket logic
- Update server.js to import modules
- Test that existing functionality works
1.3 Dependencies
Add to backend/package.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 managementconnect-redis: Session store (persistent, production-ready)redis: Session backendopenid-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:
{
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):
OIDC_ISSUER=https://auth.sneakercloud.de/application/o/claude-web-ui/
OIDC_CLIENT_ID=<from_authentik>
OIDC_CLIENT_SECRET=<from_authentik>
OIDC_REDIRECT_URI=https://agents.sneakercloud.de/auth/callback
SESSION_SECRET=<generate_with_openssl>
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
{
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/userreturns 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.userwith user data - Return 401 if not authenticated
requireGroup(groups)
- Check if
req.user.groupsincludes 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:
// 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
- Parse cookies from upgrade request
- Load session from Redis
- Validate session
- Attach user to WebSocket connection
- Reject upgrade if not authenticated
Implementation:
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:
{
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/loginfor 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:
{
user: null | {
id: string,
email: string,
name: string,
groups: string[]
},
isLoading: boolean,
error: string | null
}
Methods:
login()- Redirect to/auth/loginlogout()- Call/auth/logout, clear staterefreshUser()- Call/auth/userto get current usercheckAuth()- Verify authentication on mount
Auto-refresh:
- Poll
/auth/userevery 5 minutes - Handle 401 by redirecting to login
- Update user state on success
4.2 Login Flow
Current: App loads directly to ChatPanel
New:
- App mounts
- AuthContext checks
/auth/user - If 401: Show login page
- If 200: Show app
- User clicks "Login with Authentik"
- Redirect to
/auth/login - Authentik authentication
- Callback redirects to
/ - AuthContext refreshes user
- 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:
function App() {
return (
<AuthProvider>
<AuthenticatedApp />
</AuthProvider>
);
}
function AuthenticatedApp() {
const { user, isLoading } = useAuth();
if (isLoading) return <LoadingSpinner />;
if (!user) return <LoginPage />;
return (
<SessionProvider>
<AppContent />
</SessionProvider>
);
}
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-limitto 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:
# OIDC
OIDC_ISSUER=https://auth.sneakercloud.de/application/o/claude-web-ui/
OIDC_CLIENT_ID=<client_id>
OIDC_CLIENT_SECRET=<client_secret>
OIDC_REDIRECT_URI=https://agents.sneakercloud.de/auth/callback
# Session
SESSION_SECRET=<generate_with_openssl_rand_hex_32>
SESSION_DOMAIN=.sneakercloud.de
SESSION_SECURE=true
SESSION_MAX_AGE=86400000 # 24 hours
# Redis
REDIS_URL=redis://localhost:6379
REDIS_PASSWORD=<optional>
# App
NODE_ENV=production
FRONTEND_URL=https://agents.sneakercloud.de
docker-compose.yml Updates:
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 systemdocs/SETUP.md- Step-by-step Authentik setupdocs/CONFIGURATION.md- Environment variablesdocs/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
-
Security:
- Zero XSS vulnerabilities (httpOnly cookies)
- Zero unauthorized access incidents
- All tokens encrypted at rest
-
User Experience:
- Seamless login flow (<5 seconds)
- Clear error messages
- No unnecessary re-authentication
-
Reliability:
- 99.9% authentication availability
- Session persistence across restarts
- Graceful degradation on errors
-
Performance:
- <100ms auth middleware overhead
- No impact on WebSocket latency
- Redis session lookups <10ms
Rollback Plan
If Critical Issues Arise:
- Immediate: Disable authentication middleware
- Revert: Return to unauthenticated mode
- Investigate: Review logs and error reports
- Fix: Address issues in isolated environment
- Redeploy: With fixes applied
Feature Flags:
AUTH_ENABLED=falseto 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