Files
claude-web-ui/.prompts/001-oidc-auth-plan/oidc-auth-plan.md
Nikolas Syring 1186cb1b5e feat: Add OIDC authentication with Authentik integration
- 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>
2025-12-18 06:07:22 +01:00

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
  • 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:

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:

  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:

{
  "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:

{
  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
{
  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:

// 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:

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/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:

{
  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:

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-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:

# 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 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