Zitadel
OIDC/SSO identity provider for RavenmaskOS.
Overview
Zitadel provides centralized authentication using OIDC/OAuth2, serving as the single source of truth for user identity across all services.
| Property | Value |
|---|---|
| Image | ghcr.io/zitadel/zitadel:latest |
| Container | zitadel |
| URL | auth.ravenhelm.dev |
| Port | 8080 (internal) |
| Config | ~/ravenhelm/services/zitadel/ |
| Database | PostgreSQL (zitadel database) |
Architecture
External Request
↓
Traefik (TLS)
↓
Zitadel:8080
↓
PostgreSQL:5432
(zitadel DB)
OIDC Endpoints
| Endpoint | URL |
|---|---|
| Issuer | https://auth.ravenhelm.dev |
| Discovery | https://auth.ravenhelm.dev/.well-known/openid-configuration |
| Authorization | https://auth.ravenhelm.dev/oauth/v2/authorize |
| Token | https://auth.ravenhelm.dev/oauth/v2/token |
| Userinfo | https://auth.ravenhelm.dev/oidc/v1/userinfo |
| JWKS | https://auth.ravenhelm.dev/oauth/v2/keys |
| End Session | https://auth.ravenhelm.dev/oidc/v1/end_session |
Registered Applications
| Application | Client ID | Type | Redirect URI |
|---|---|---|---|
| OAuth2-Proxy | 351314046345084937 | Web (PKCE) | https://oauth.ravenhelm.dev/oauth2/callback |
| Norns Admin | 350642335773687817 | Web | https://norns.ravenhelm.dev/api/auth/callback/zitadel |
| Grafana | 351369960024506377 | Web | https://grafana.ravenhelm.dev/login/generic_oauth |
| GitLab | 351312537486163977 | Web | https://gitlab.ravenhelm.dev/users/auth/openid_connect/callback |
| Bifrost | (configured) | Web | https://bifrost.ravenhelm.dev/api/auth/callback/zitadel |
Admin Access
URL: https://auth.ravenhelm.dev
Username: admin@ravenhelm.auth.ravenhelm.dev
Password: (stored in 1Password: "Zitadel Admin")
Configuration
docker-compose.yml
services:
zitadel:
image: ghcr.io/zitadel/zitadel:latest
container_name: zitadel
restart: unless-stopped
command: 'start-from-init --masterkey "${ZITADEL_MASTERKEY}" --tlsMode external'
environment:
ZITADEL_DATABASE_POSTGRES_HOST: postgres
ZITADEL_DATABASE_POSTGRES_PORT: 5432
ZITADEL_DATABASE_POSTGRES_DATABASE: zitadel
ZITADEL_DATABASE_POSTGRES_USER_USERNAME: ${POSTGRES_USER}
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: ${POSTGRES_PASSWORD}
ZITADEL_EXTERNALSECURE: "true"
ZITADEL_EXTERNALPORT: 443
ZITADEL_EXTERNALDOMAIN: auth.ravenhelm.dev
ZITADEL_FIRSTINSTANCE_ORG_NAME: ravenhelm
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_USERNAME: admin
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORD: ${ZITADEL_ADMIN_PASSWORD}
ZITADEL_MASTERKEY: ${ZITADEL_MASTERKEY}
networks:
- ravenhelm_net
labels:
- "traefik.enable=true"
- "traefik.http.routers.zitadel.rule=Host(`auth.ravenhelm.dev`)"
- "traefik.http.routers.zitadel.entrypoints=websecure"
- "traefik.http.routers.zitadel.tls.certresolver=letsencrypt"
- "traefik.http.services.zitadel.loadbalancer.server.port=8080"
- "traefik.http.services.zitadel.loadbalancer.server.scheme=h2c"
Environment Variables
| Variable | Purpose |
|---|---|
ZITADEL_MASTERKEY | Encryption key for secrets (32 hex chars) |
ZITADEL_ADMIN_PASSWORD | Initial admin user password |
ZITADEL_EXTERNALDOMAIN | Public domain for OIDC issuer |
ZITADEL_EXTERNALSECURE | Enable HTTPS (true) |
ZITADEL_EXTERNALPORT | Public port (443) |
Add New OIDC Application
Via Web UI
- Login to auth.ravenhelm.dev
- Navigate to Organization → Projects → Select project
- Click + New Application
- Choose Web Application
- Select authentication method:
- PKCE (recommended for SPAs/public clients)
- Code (for confidential clients with secrets)
- Configure:
- Redirect URIs:
https://service.ravenhelm.dev/callback - Post Logout URIs:
https://service.ravenhelm.dev
- Redirect URIs:
- Save and copy Client ID (and Secret if applicable)
Common Redirect URI Patterns
| Framework | Pattern |
|---|---|
| NextAuth.js | /api/auth/callback/zitadel |
| OAuth2-Proxy | /oauth2/callback |
| Grafana | /login/generic_oauth |
| GitLab | /users/auth/openid_connect/callback |
Email Configuration
SMTP configured for AWS SES:
# /data/zitadel/smtp-setup.yaml
smtp:
host: email-smtp.us-east-1.amazonaws.com
port: 587
tls: true
from: noreply@ravenhelm.dev
fromName: RavenmaskOS
user: (AWS SES credentials)
password: (AWS SES credentials)
Quick Commands
# Check health
docker exec zitadel /app/zitadel ready
# View logs
docker logs -f zitadel
# Restart
docker restart zitadel
# Test OIDC discovery
curl -s https://auth.ravenhelm.dev/.well-known/openid-configuration | jq .
# Check database connection
docker exec -i postgres psql -U ravenhelm -d zitadel -c "SELECT COUNT(*) FROM projections.users8;"
Database
Zitadel uses a dedicated PostgreSQL database:
# Access Zitadel database
docker exec -i postgres psql -U ravenhelm -d zitadel
# Check user count
SELECT COUNT(*) FROM projections.users8;
# List organizations
SELECT id, name FROM projections.orgs1;
# List applications
SELECT id, name, client_id FROM projections.apps7_oidc_configs;
Troubleshooting
Login Loop
Symptoms: Redirects back to login after authenticating
Diagnosis:
docker logs zitadel 2>&1 | grep -i "error\|redirect"
Solutions:
- Verify redirect URI matches exactly (case-sensitive, no trailing slash)
- Check Client ID/Secret in service configuration
- Clear browser cookies for
.ravenhelm.dev - Verify
ZITADEL_EXTERNALDOMAINmatches URL
Token Invalid
Symptoms: Services reject Zitadel tokens
Diagnosis:
# Test token endpoint
curl -X POST https://auth.ravenhelm.dev/oauth/v2/token \
-d "grant_type=client_credentials" \
-d "client_id=<id>" \
-d "client_secret=<secret>"
Solutions:
- Verify EXTERNALDOMAIN matches public URL
- Check TLS termination (Traefik should handle)
- Verify token scopes match application config
Database Connection Failed
Symptoms: Zitadel won't start, database errors
Diagnosis:
docker logs zitadel 2>&1 | grep -i "postgres\|database"
Solutions:
- Verify PostgreSQL is running:
docker ps | grep postgres - Check database exists:
docker exec postgres psql -U ravenhelm -l | grep zitadel - Verify credentials in
.envfile
Master Key Issues
Symptoms: Encryption/decryption errors
Solutions:
- Master key must be exactly 32 hex characters
- Key cannot be changed after initialization
- If key is lost, Zitadel must be reinitialized (data loss)
Backup & Restore
Backup
# Backup Zitadel database
docker exec postgres pg_dump -U ravenhelm -d zitadel | gzip > zitadel_$(date +%Y%m%d).sql.gz
Restore
# Restore (requires dropping existing)
docker exec postgres dropdb -U ravenhelm zitadel
docker exec postgres createdb -U ravenhelm zitadel
gunzip -c zitadel_backup.sql.gz | docker exec -i postgres psql -U ravenhelm -d zitadel