Workbench Authentication Setup Guide

This guide covers authentication configuration for the SEA Workbench across different environments (development, staging, production).

Overview

The Workbench uses a dual-layer authentication architecture:

Reference:

Architecture

1
2
3
4
5
6
7
8
9
10
11
┌─────────────────┐     OIDC Flow      ┌──────────┐
│   Workbench UI  │ ←───────────────→  │ Zitadel  │
│  (Vite/React)   │                     │ (OIDC)   │
└────────┬────────┘                     └──────────┘
         │                                        │
         │ Bearer Token                           │
         ↓                                        │
┌─────────────────┐                              │
│  Workbench BFF  │ ──────────────────────────────┘
│   (FastAPI)     │      JWT Validation
└─────────────────┘

Environment Types

Environment Frontend Provider Backend Provider Mock Mode
Development zitadel/mock stub Allowed
Staging zitadel oidc Disabled
Production oidc oidc Disabled

Local Development Setup

Option 1: Mock Authentication (Fastest)

For UI development without authentication overhead:

Frontend (.env.local)

1
2
VITE_AUTH_PROVIDER=mock
VITE_AUTH_MOCK=true

Backend (Environment)

1
2
AUTH_PROVIDER=stub
ENVIRONMENT=development

This bypasses all OIDC flows and returns a mock user with viewer role.

Option 2: Local Zitadel (Realistic)

For testing authentication with a local Zitadel instance:

1. Start Zitadel via Docker

1
2
3
4
5
docker run -d \
  --name zitadel \
  -p 8080:8080 \
  -e ZITADEL_DATABASE_STROAGE_POSTGRES_HOST=db \
  ghcr.io/zitadel/zitadel:latest

2. Configure Frontend (.env.local)

1
2
3
4
5
6
VITE_AUTH_PROVIDER=zitadel
VITE_AUTH_MOCK=false
VITE_OIDC_AUTHORITY=http://localhost:8080
VITE_OIDC_CLIENT_ID=your-client-id
VITE_OIDC_REDIRECT_URI=http://localhost:3000/callback
VITE_OIDC_POST_LOGOUT_URI=http://localhost:3000

3. Configure Backend

1
2
3
4
5
AUTH_PROVIDER=oidc
OIDC_ISSUER_URL=http://localhost:8080
OIDC_CLIENT_ID=your-client-id
OIDC_AUDIENCE=your-audience
ENVIRONMENT=development

Option 3: Test OIDC Provider

For testing with a public OIDC provider (Auth0, Keycloak, etc.):

Frontend (.env.local)

1
2
3
4
5
VITE_AUTH_PROVIDER=oidc
VITE_AUTH_MOCK=false
VITE_OIDC_AUTHORITY=https://your-issuer.auth0.com
VITE_OIDC_CLIENT_ID=your-client-id
VITE_OIDC_REDIRECT_URI=http://localhost:3000/callback

Backend

1
2
3
4
AUTH_PROVIDER=oidc
OIDC_ISSUER_URL=https://your-issuer.auth0.com
OIDC_CLIENT_ID=your-client-id
OIDC_AUDIENCE=your-api-identifier

Staging Environment Configuration

Staging should mirror production as closely as possible:

Frontend (.env.production)

1
2
3
4
5
VITE_AUTH_PROVIDER=zitadel
VITE_AUTH_MOCK=false
VITE_OIDC_AUTHORITY=https://staging-auth.your-domain.com
VITE_OIDC_CLIENT_ID=staging-client-id
VITE_OIDC_REDIRECT_URI=https://staging.workbench.your-domain.com/callback

Backend (Environment Variables)

1
2
3
4
5
ENVIRONMENT=staging
AUTH_PROVIDER=oidc
OIDC_ISSUER_URL=https://staging-auth.your-domain.com
OIDC_CLIENT_ID=staging-client-id
OIDC_AUDIENCE=staging-audience

Key Points:


Production Environment Configuration

CRITICAL: Production enforces OIDC-only authentication with fail-fast validation.

Production Checklist

Frontend Configuration

.env.production

1
2
3
4
5
6
VITE_AUTH_PROVIDER=oidc
VITE_AUTH_MOCK=false
VITE_OIDC_AUTHORITY=https://auth.your-domain.com
VITE_OIDC_CLIENT_ID=production-client-id
VITE_OIDC_REDIRECT_URI=https://workbench.your-domain.com/callback
VITE_OIDC_POST_LOGOUT_URI=https://workbench.your-domain.com

Backend Configuration

Environment Variables

1
2
3
4
5
ENVIRONMENT=production
AUTH_PROVIDER=oidc
OIDC_ISSUER_URL=https://auth.your-domain.com
OIDC_CLIENT_ID=production-client-id
OIDC_AUDIENCE=production-audience

Fail-Fast Validation

Both frontend and backend enforce production safety:

Backend (services/workbench-bff/src/api/auth.py)

1
2
3
4
5
6
7
8
9
10
def validate_auth_config():
    """Fail-fast validation on app startup"""
    env = os.getenv("ENVIRONMENT", "development")
    if env == "production":
        if os.getenv("AUTH_PROVIDER") != "oidc":
            raise ValueError("AUTH_PROVIDER must be 'oidc' in production")
        required_vars = ["OIDC_ISSUER_URL", "OIDC_CLIENT_ID", "OIDC_AUDIENCE"]
        missing = [v for v in required_vars if not os.getenv(v)]
        if missing:
            raise ValueError(f"Missing required OIDC variables: {missing}")

Frontend (apps/workbench/src/lib/auth-factory.ts)

1
2
3
4
5
6
7
8
if (import.meta.env.MODE === 'production') {
  const provider = import.meta.env.VITE_AUTH_PROVIDER;
  const mockEnabled = import.meta.env.VITE_AUTH_MOCK === 'true';

  if ((provider === 'zitadel' || provider === 'oidc') && mockEnabled) {
    throw new Error('VITE_AUTH_MOCK cannot be enabled in production with OIDC provider');
  }
}

Zitadel Setup Guide

1. Create Project

In Zitadel Console:

  1. Navigate to ProjectsNew Project
  2. Name: SEA Workbench
  3. Save the Project ID for configuration

2. Create Application

  1. In the project, go to ApplicationsNew
  2. Type: Web
  3. Name: workbench-ui
  4. Redirect URIs:
  5. Post Logout URIs:
  6. Auth Method: PKCE
  7. Save and note the Client ID

3. Configure Roles

  1. Go to SettingsRoles
  2. Create roles: viewer, operator, admin
  3. Assign roles to users as needed

4. Create API Client (for BFF)

  1. Create another application of type Machine
  2. Note the Client ID and Client Secret
  3. Use these values for backend OIDC configuration

Common Misconfigurations

Issue 1: CORS Errors

Symptom: Browser blocks login redirect with CORS error

Solution:

Issue 2: Invalid JWT Signature

Symptom: Backend rejects token with signature verification error

Solution:

Issue 3: Mock Mode in Production

Symptom: Build fails with error about VITE_AUTH_MOCK in production

Solution:

1
2
# Set this explicitly in production build
VITE_AUTH_MOCK=false

The frontend will throw a build error if mock mode is enabled in production.

Issue 4: Missing Required Environment Variables

Symptom: Backend crashes on startup with ValueError

Solution: Ensure all required variables are set when ENVIRONMENT=production:

1
2
3
4
5
ENVIRONMENT=production
AUTH_PROVIDER=oidc
OIDC_ISSUER_URL=https://...
OIDC_CLIENT_ID=your-client-id
OIDC_AUDIENCE=your-audience

Issue 5: Role-Based Access Not Working

Symptom: User can’t access features despite having roles

Solution:

  1. Check Zitadel user has roles assigned
  2. Verify roles are in JWT token (use jwt.io to decode)
  3. Check backend role extraction logic in auth.py
  4. Ensure frontend is passing Authorization header

Testing Authentication

Test Frontend Login

1
2
3
4
5
6
7
# Start with mock mode
npm run dev
# Should login automatically with mock user

# Start with Zitadel
VITE_AUTH_PROVIDER=zitadel VITE_AUTH_MOCK=false npm run dev
# Should redirect to Zitadel login

Test Backend Validation

1
2
3
4
5
# With stub provider
curl -H "X-User-Id: testuser" http://localhost:8010/health

# With OIDC provider
curl -H "Authorization: Bearer <jwt-token>" http://localhost:8010/health

Verify Production Safety

1
2
3
4
5
6
7
# Set production environment
export ENVIRONMENT=production

# Remove AUTH_PROVIDER (should fail)
unset AUTH_PROVIDER
python services/workbench-bff/main.py
# Expected: ValueError: AUTH_PROVIDER must be 'oidc' in production

Security Best Practices

  1. Never commit secrets: Use .env.local for local development only
  2. Rotate secrets regularly: Update OIDC client secrets periodically
  3. Use HTTPS in production: Never use HTTP for OIDC in production
  4. Enable JWKS verification: Backend must verify JWT signatures
  5. Limit token lifetimes: Configure short-lived tokens in Zitadel
  6. Implement token refresh: Use refresh tokens instead of long-lived access tokens
  7. Audit logs: Monitor authentication attempts and failures
  8. Role principle of least privilege: Default to viewer role

Troubleshooting Commands

Check Current Configuration

1
2
3
4
5
# Frontend
grep VITE_AUTH .env.local

# Backend
env | grep -E '(AUTH_PROVIDER|OIDC|ENVIRONMENT)'

Test OIDC Discovery

1
2
curl https://your-issuer.com/.well-known/openid-configuration
curl https://your-issuer.com/.well-known/jwks.json

Decode JWT Token

1
2
# Decode and view token claims
echo "your-jwt-token" | jq -R 'split(".") | .[1] | @base64d | fromjson'

Additional Resources


Support

For authentication issues:

  1. Check this guide’s Common Misconfigurations section
  2. Review Zitadel application logs
  3. Check browser console for frontend errors
  4. Review backend logs (services/workbench-bff/logs/)
  5. Consult SDS-054 for security model details