This guide covers setting up Keycloak for OpenID Connect (OIDC) authentication with OpenTranscribe.
Keycloak provides enterprise-grade identity and access management. OpenTranscribe integrates with Keycloak via OIDC, allowing:
- Single Sign-On (SSO) with your organization's identity provider
- Role-based access control synchronized from Keycloak
- Support for LDAP/AD federation through Keycloak
- Social login providers (Google, GitHub, etc.) via Keycloak
- Full OIDC discovery support (endpoints auto-populated from provider metadata)
- Federated logout — when a user's OpenTranscribe session ends, the logout is propagated to Keycloak
v0.4.0 Change: Keycloak configuration is now managed via the Super Admin UI (Settings → Authentication → Keycloak/OIDC). Settings are stored encrypted (AES-256-GCM) in the database. Environment variables continue to work as an initial fallback but database config takes precedence.
MFA Note: Keycloak users bypass local MFA — their identity provider is responsible for multi-factor authentication. Configure MFA enforcement directly in Keycloak.
Recommended Method (using opentr.sh):
# Start OpenTranscribe with Keycloak test container
./opentr.sh start dev --with-keycloak-test
# Or for production mode (testing before push)
./opentr.sh start prod --build --with-keycloak-testAdvanced Method (manual docker compose):
# Start Keycloak dev server (from OpenTranscribe root directory)
docker compose -f docker-compose.yml -f docker-compose.keycloak.yml up -d keycloak
# Check status
docker compose -f docker-compose.yml -f docker-compose.keycloak.yml ps keycloak
# View logs
docker compose -f docker-compose.yml -f docker-compose.keycloak.yml logs -f keycloakAccess the admin console at http://localhost:8180
Default credentials: admin / admin
- Log in to Keycloak admin console
- Click the dropdown at the top-left (shows "master")
- Click "Create Realm"
- Realm name:
opentranscribe - Click "Create"
-
Navigate to Clients in the left sidebar
-
Click Create client
-
Configure the client:
- Client type: OpenID Connect
- Client ID:
opentranscribe-app - Click Next
-
Capability config:
- Client authentication: ON (confidential client)
- Authorization: OFF
- Click Next
-
Login settings:
- Valid redirect URIs:
http://localhost:5173/login(local dev)http://your-server-ip/login(LAN access - replace with your IP)https://yourdomain.com/login(production)
- Valid post logout redirect URIs:
+ - Web origins:
+(allows all origins from redirect URIs)
IMPORTANT: Redirect URIs must point to the FRONTEND login page, not the backend API endpoint.
- Valid redirect URIs:
-
Click Save
- Navigate to Clients → opentranscribe-app
- Go to the Credentials tab
- Copy the Client secret value
- Navigate to Realm roles in the left sidebar
- Click Create role
- Create role with name:
user - Click Save
- Repeat to create role:
admin
- Navigate to Users in the left sidebar
- Click Add user
- Fill in the details:
- Username:
testuser - Email:
testuser@example.com - Email verified: ON
- First name:
Test - Last name:
User
- Username:
- Click Create
- Go to the Credentials tab
- Click Set password
- Enter a password
- Temporary: OFF
- Click Save
- Go to the Role mapping tab
- Click Assign role
- Select
userrole (oradminfor admin access) - Click Assign
Recommended Method: Via Admin UI (stores config encrypted in database — takes precedence over .env)
- Log in to OpenTranscribe as a super admin
- Go to Settings → Authentication → Keycloak/OIDC
- Enable Keycloak/OIDC
- Configure the following settings:
- Server URL:
http://localhost:8180(must be accessible from user's browser) - Internal URL:
http://transcribe-app-keycloak-1:8080(for backend-to-Keycloak communication) - Realm:
opentranscribe - Client ID:
opentranscribe-app - Client Secret: Paste the secret from Step 4
- Callback URL:
http://localhost:5173/login(FRONTEND login page, NOT backend API) - Admin Role:
admin
- Server URL:
- Click Save
CRITICAL - For LAN Access: If accessing OpenTranscribe from other devices on your network (e.g., from Mac on local network), use your server's IP address:
- Server URL:
http://192.168.x.x:8180(replace with your server IP)- Callback URL:
http://192.168.x.x/login(match your server IP)The callback URL must be accessible from the user's browser and must point to the frontend login page.
Alternative Method: Via .env file (initial seed fallback — only used when no database config exists)
If the Admin UI is not yet accessible (e.g., first-time setup), environment variables can seed the initial configuration:
# Keycloak/OIDC Configuration
KEYCLOAK_ENABLED=true
KEYCLOAK_SERVER_URL=http://localhost:8180 # Must be accessible from browser
KEYCLOAK_INTERNAL_URL=http://transcribe-app-keycloak-1:8080
KEYCLOAK_REALM=opentranscribe
KEYCLOAK_CLIENT_ID=opentranscribe-app
KEYCLOAK_CLIENT_SECRET=<paste-client-secret-from-step-4>
KEYCLOAK_CALLBACK_URL=http://localhost:5173/login # Frontend login page, NOT backend API
KEYCLOAK_ADMIN_ROLE=admin
KEYCLOAK_TIMEOUT=30Note: Database configuration (via admin UI) takes precedence over .env variables.
If configured via Admin UI: Changes take effect immediately — no restart required.
If configured via .env only: Restart the backend to load the new environment variables:
./opentr.sh stop
./opentr.sh start dev- Open http://localhost:5173 (OpenTranscribe frontend)
- Click "Sign in with Keycloak"
- You'll be redirected to Keycloak login
- Enter test user credentials
- After successful login, you'll be redirected back to OpenTranscribe
For production, use a properly secured Keycloak instance:
# .env for production
KEYCLOAK_ENABLED=true
KEYCLOAK_SERVER_URL=https://keycloak.yourdomain.com
KEYCLOAK_REALM=opentranscribe
KEYCLOAK_CLIENT_ID=opentranscribe-app
KEYCLOAK_CLIENT_SECRET=<secure-secret>
KEYCLOAK_CALLBACK_URL=https://yourdomain.com/login # Frontend login page
KEYCLOAK_ADMIN_ROLE=admin- HTTPS Required: Always use HTTPS in production
- Client Secret: Stored encrypted (AES-256-GCM) in database when configured via Admin UI; never commit to git
- Token Validation: OpenTranscribe validates tokens using Keycloak's JWKS endpoint (auto-discovered via OIDC metadata)
- Role Mapping: Only users with the configured
KEYCLOAK_ADMIN_ROLEget admin access - MFA: Keycloak users bypass OpenTranscribe's local MFA — configure MFA enforcement in your Keycloak realm
- Federated Logout: When a user logs out of OpenTranscribe, the logout is propagated to Keycloak so the Keycloak SSO session is also terminated
To use Keycloak with your existing Active Directory:
- Go to User Federation in Keycloak admin
- Add LDAP provider
- Configure your AD connection settings
- Users can then log in to OpenTranscribe using their AD credentials via Keycloak
To enable Google, GitHub, etc.:
- Go to Identity Providers in Keycloak admin
- Add desired provider (e.g., Google)
- Configure OAuth credentials from the provider
- Users can then use social login to access OpenTranscribe
"Keycloak authentication is not enabled"
- If using Admin UI: verify Keycloak/OIDC is enabled in Settings → Authentication → Keycloak/OIDC
- If using .env: ensure
KEYCLOAK_ENABLED=trueand restart the backend - Database config takes precedence — an explicit
enabled=falsein the database overrides atruein .env
"Invalid or expired state parameter"
- Try the login again (state tokens expire after 10 minutes)
- Clear browser cookies and try again
"Failed to exchange authorization code"
- Verify client secret is correct
- Check Keycloak logs:
docker compose logs keycloak - Ensure callback URL matches exactly
Keycloak login page doesn't load or loads slowly
- Check that
KEYCLOAK_SERVER_URLis accessible from your browser (not just from the server) - For LAN access, use server IP address (e.g.,
http://192.168.x.x:8180) instead oflocalhost - Update Keycloak client redirect URIs to include your access URL
- Verify with:
curl http://your-server-ip:8180/realms/opentranscribe/.well-known/openid-configuration
Browser shows raw JSON instead of logging in
- This means the callback URL is pointing to the backend API instead of frontend
- Callback URL MUST be:
http://your-domain/login(frontend page) - NOT:
http://your-domain/api/auth/keycloak/callback(backend API) - Update via Admin UI → Settings → Authentication → Keycloak → Callback URL
User created but has wrong role
- Verify role mapping in Keycloak user's "Role mapping" tab
- Check
KEYCLOAK_ADMIN_ROLEmatches your Keycloak role name exactly
Enable debug logging in OpenTranscribe:
# In backend container
export LOG_LEVEL=DEBUGCheck backend logs for authentication details:
./opentr.sh logs backendFor government deployments (DoD, federal agencies) Keycloak can act as the X.509/PKI broker — it validates CAC/PIV certificates via mTLS at the edge and injects certificate metadata into the OIDC token as claims. OpenTranscribe consumes those claims to populate the user record and enforce PKI-based admin access.
Browser/CAC Reader → Keycloak (mTLS) → OIDC token with cert claims → OpenTranscribe
- The user authenticates to Keycloak using their CAC/PIV certificate
- Keycloak's X.509 authenticator validates the certificate chain
- Cert metadata is injected into the OIDC access token as claims
- OpenTranscribe extracts those claims and stores them on the user record
- Admin status is granted if the cert DN is in
PKI_ADMIN_DNSor the Keycloak realm role matchesKEYCLOAK_ADMIN_ROLE
- In the Keycloak admin console, go to Authentication → Flows
- Copy the browser flow and name it (e.g.
browser-x509) - Add the X509/Validate Username Form execution to the form
- Set it to ALTERNATIVE (allows fallback to password for non-PKI users)
- Configure the execution:
- User Identity Source: Subject's Common Name
- Canonical DN representation enabled: off (pass DN as-is)
- Certificate revocation checking: OCSP or CRL per your PKI policy
- Bind the flow to your realm under Authentication → Bindings → Browser Flow
Keycloak injects cert metadata using either the short names or the x509_cert_*
prefix — OpenTranscribe handles both automatically:
| Short name | x509_cert_* alias | Stored on user |
|---|---|---|
cert_dn |
x509_cert_dn |
pki_subject_dn |
cert_serial |
x509_cert_serial |
pki_serial_number |
cert_issuer |
x509_cert_issuer |
pki_issuer_dn |
cert_org |
x509_cert_org |
pki_organization |
cert_ou |
x509_cert_ou |
pki_organizational_unit |
cert_valid_from |
x509_cert_not_before |
pki_not_before |
cert_valid_until |
x509_cert_not_after |
pki_not_after |
cert_fingerprint |
x509_cert_sha256_fingerprint |
pki_fingerprint_sha256 |
Add a Protocol Mapper to your client to include these claims in the access token.
Government certificates use a space-separated CN: CN=LastName FirstName emailusername
Example: CN=Doe John jdoe,OU=Agency,O=U.S. Government,C=US
OpenTranscribe parses this format automatically — the display name will be
rendered as John Doe and the email username token (jdoe) is used for
account lookup only.
Users whose certificate DN appears in PKI_ADMIN_DNS receive admin access
regardless of their Keycloak realm role. This allows government system owners
to control admin access via PKI policy rather than requiring manual Keycloak
role assignments.
Important: Use semicolons to separate multiple DNs — commas are part of DN syntax and cannot be used as a list separator:
# Single admin DN
PKI_ADMIN_DNS=CN=Doe John jdoe,OU=Agency,O=U.S. Government,C=US
# Multiple admin DNs — semicolon separated
PKI_ADMIN_DNS=CN=Doe John jdoe,OU=Agency,O=U.S. Government,C=US;CN=Smith Jane jsmith,OU=Agency,O=U.S. Government,C=US| Variable | Description |
|---|---|
PKI_ADMIN_DNS |
Semicolon-separated list of cert DNs that grant admin access |
KEYCLOAK_ADMIN_ROLE |
Keycloak realm role that grants admin (default: admin) |
KEYCLOAK_VERIFY_ISSUER |
Validate token issuer against realm URL (default: true) |
KEYCLOAK_VERIFY_AUDIENCE |
Validate token audience against client ID (default: false) |
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ │ │ │ │ │
│ OpenTranscribe │────▶│ Keycloak │────▶│ LDAP/AD/ │
│ Frontend │ │ (OIDC IdP) │ │ Social Login │
│ │ │ │ │ │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│ │
│ │
▼ ▼
┌─────────────────┐ ┌──────────────────┐
│ │ │ │
│ OpenTranscribe │◀────│ Token Validation│
│ Backend │ │ (JWKS/JWT) │
│ │ │ │
└─────────────────┘ └──────────────────┘
- User clicks "Sign in with Keycloak"
- Frontend requests authorization URL from backend
- User is redirected to Keycloak login
- After login, Keycloak redirects back with authorization code
- Backend exchanges code for tokens
- Backend validates tokens using Keycloak's JWKS
- User is created/updated in OpenTranscribe database
- OpenTranscribe issues its own JWT for session management