This guide provides step-by-step instructions for configuring Keycloak as the OIDC provider for PKI Manager.
- Overview
- Prerequisites
- Keycloak Configuration
- PKI Manager Configuration
- Testing the Integration
- Troubleshooting
- E2E Testing Configuration
PKI Manager uses OpenID Connect (OIDC) for authentication. Keycloak serves as the identity provider, handling:
- User authentication via Authorization Code Flow with PKCE
- Token issuance (access tokens, refresh tokens, ID tokens)
- User session management
- Optional: Machine-to-machine (M2M) authentication via Client Credentials flow
- Keycloak server (version 20+) accessible via HTTPS
- Admin access to Keycloak
- PKI Manager deployed and accessible
- Navigate to your Keycloak admin URL (e.g.,
https://your-keycloak-domain/admin) - Log in with administrator credentials
Realms isolate users and applications. For PKI Manager, create a dedicated realm or use an existing one.
- Click the realm dropdown in the top-left sidebar
- Click Create realm or select an existing realm (e.g.,
pki-manager)
The pki-web client is a public client for the frontend SPA application using PKCE.
- In the sidebar, click Clients
- Click Create client
Configure the basic client information:
| Field | Value |
|---|---|
| Client type | OpenID Connect |
| Client ID | pki-web |
| Name | PKI Manager Web |
| Description | Public SPA client for PKI Manager frontend with PKCE |
Click Next.
Configure the client authentication and flows:
| Setting | Value | Reason |
|---|---|---|
| Client authentication | Off | Public client (SPA cannot securely store secrets) |
| Authorization | Off | Not needed for basic authentication |
| Standard flow | Enabled | Authorization Code Flow with PKCE |
| Direct access grants | Enabled | Optional: allows password grant for testing |
| Implicit flow | Disabled | Not recommended for SPAs |
| Service accounts roles | Disabled | Not applicable for public clients |
Click Next.
Configure the redirect URIs and web origins:
| Field | Value | Description |
|---|---|---|
| Root URL | https://pki.example.com |
Your frontend URL |
| Home URL | https://pki.example.com |
Landing page after logout |
| Valid redirect URIs | https://pki.example.com/* |
Allowed redirect destinations |
| Valid post logout redirect URIs | https://pki.example.com/* |
Allowed post-logout destinations |
| Web origins | https://pki.example.com |
CORS allowed origins |
Click Save.
The client is now created and enabled.
Create a user to test the authentication flow.
- In the sidebar, click Users
- Click Create new user
| Field | Value |
|---|---|
| Username | testuser |
testuser@example.com |
|
| First name | Test |
| Last name | User |
Click Create.
- Go to the Credentials tab
- Click Set password
- Enter password and confirmation
- Set Temporary to Off (user won't be forced to change password)
- Click Save
For machine-to-machine authentication (API access without user interaction):
-
Create a new client with:
- Client ID:
pki-service - Client authentication: On (confidential client)
- Service accounts roles: Enabled
- All other flows: Disabled
- Client ID:
-
After creation, go to Credentials tab to get the client secret
-
Add to backend
OIDC_AUDIENCE:pki-web,pki-service
Add these environment variables to your backend .env file:
# OIDC Authentication
OIDC_ISSUER=https://your-keycloak-domain/realms/pki-manager
OIDC_AUDIENCE=pki-web,pki-service
OIDC_ROLES_CLAIM=realm_access.roles| Variable | Description |
|---|---|
OIDC_ISSUER |
Keycloak realm URL (must match iss claim in tokens) |
OIDC_AUDIENCE |
Expected aud claim(s), comma-separated |
OIDC_ROLES_CLAIM |
JSON path to roles in the JWT token |
Add to frontend .env before building:
VITE_OIDC_AUTHORITY=https://your-keycloak-domain/realms/pki-manager
VITE_OIDC_CLIENT_ID=pki-web
VITE_OIDC_SCOPE=openid profile emailMount a config.json file:
{
"apiUrl": "https://api.pki.example.com/trpc",
"oidc": {
"authority": "https://your-keycloak-domain/realms/pki-manager",
"clientId": "pki-web",
"scope": "openid profile email"
}
}Add volume mount in docker-compose.yml:
services:
frontend:
volumes:
- ./config.json:/usr/share/nginx/html/config.json:roExample .env for Docker deployment:
# Backend OIDC
OIDC_ISSUER=https://your-keycloak-domain/realms/pki-manager
OIDC_AUDIENCE=pki-web,pki-service
OIDC_ROLES_CLAIM=realm_access.roles
# Frontend (if not using runtime config.json)
VITE_OIDC_AUTHORITY=https://your-keycloak-domain/realms/pki-manager
VITE_OIDC_CLIENT_ID=pki-web-
Access PKI Manager: Navigate to
https://pki.example.com -
Login Redirect: You should be redirected to Keycloak login page
-
Authenticate: Enter test user credentials
-
Callback: After successful login, you're redirected back to PKI Manager
-
Verify Token: Check browser DevTools → Network → look for
Authorization: Bearerheader in API calls
Cause: The redirect URI doesn't match Keycloak's allowed list.
Solution:
- Verify
Valid redirect URIsin Keycloak client settings - Ensure the frontend URL exactly matches (including protocol and trailing slashes)
Cause: OIDC_ISSUER doesn't match the iss claim in the token.
Solution:
- Check the exact Keycloak realm URL
- Ensure no trailing slash mismatch
- Format:
https://keycloak-domain/realms/realm-name
Cause: Keycloak or backend not allowing the frontend origin.
Solution:
- Add frontend URL to Keycloak client's
Web origins - Verify backend
FRONTEND_URLenvironment variable
Cause: Refresh token expired or silent renewal iframe blocked.
Solution:
- Check Keycloak realm's token lifespan settings
- Ensure
silent-renew.htmlis accessible - Verify third-party cookies aren't blocked
Cause: Token audience doesn't include the expected client ID.
Solution:
- Verify
OIDC_AUDIENCEincludespki-web - Check Keycloak client's audience mapper if customized
| Endpoint | URL Pattern |
|---|---|
| Admin Console | https://keycloak-domain/admin |
| Realm Settings | https://keycloak-domain/admin/master/console/#/realm-name |
| OIDC Discovery | https://keycloak-domain/realms/realm-name/.well-known/openid-configuration |
| Token Endpoint | https://keycloak-domain/realms/realm-name/protocol/openid-connect/token |
| UserInfo Endpoint | https://keycloak-domain/realms/realm-name/protocol/openid-connect/userinfo |
| Variable | Example | Required |
|---|---|---|
OIDC_ISSUER |
https://keycloak.example.com/realms/pki |
Yes (backend) |
OIDC_AUDIENCE |
pki-web,pki-service |
Yes (backend) |
OIDC_ROLES_CLAIM |
realm_access.roles |
No (default shown) |
VITE_OIDC_AUTHORITY |
https://keycloak.example.com/realms/pki |
Yes (frontend) |
VITE_OIDC_CLIENT_ID |
pki-web |
Yes (frontend) |
VITE_OIDC_SCOPE |
openid profile email |
No (default shown) |
PKI Manager includes a pre-configured Keycloak realm for E2E testing with RBAC verification.
The E2E environment (docker-compose.e2e.yml) includes pre-configured test users:
| Username | Password | Roles | Use Case |
|---|---|---|---|
testadmin |
Test123! |
admin |
Admin operations: create/revoke/delete CAs and certificates |
testuser |
Test123! |
user |
Regular user: view and issue certificates only |
The pki-e2e realm is automatically imported when starting the E2E Docker environment:
- Realm:
pki-e2e - Client:
pki-web(public client with PKCE) - Roles:
admin,user - Direct Access Grants: Enabled (for automated testing)
- Brute Force Protection: Disabled (to prevent test lockouts)
The E2E environment handles OIDC token validation in Docker with special configuration:
# Keycloak (v26+) - use full URL for consistent issuer
environment:
- KC_HOSTNAME=http://localhost:8180
# Backend - separate discovery URL for Docker networking
environment:
- OIDC_ISSUER=http://localhost:8180/realms/pki-e2e
- OIDC_DISCOVERY_BASE_URL=http://keycloak:8080/realms/pki-e2eWhy this is needed:
- Browser accesses Keycloak at
localhost:8180and tokens haveiss: http://localhost:8180/... - Backend container cannot reach
localhost:8180(it refers to the container itself) OIDC_DISCOVERY_BASE_URLtells the backend to fetch OIDC config from Docker network (keycloak:8080)- The backend validates tokens against
OIDC_ISSUER(what's in tokens) while fetching from the discovery URL
# Start the E2E environment
docker compose -f docker/docker-compose.e2e.yml up -d
# Wait for Keycloak to be ready (~60 seconds)
docker compose -f docker/docker-compose.e2e.yml ps
# Run RBAC tests
pnpm playwright test tests/e2e-rbac.spec.ts --reporter=list
# Cleanup
docker compose -f docker/docker-compose.e2e.yml down -v| Service | URL | Credentials |
|---|---|---|
| Keycloak Console | http://localhost:8180 | admin / admin |
| OIDC Discovery | http://localhost:8180/realms/pki-e2e/.well-known/openid-configuration | - |
For production testing, create equivalent users in your production Keycloak:
-
Create
testadminuser:- Username:
testadmin - Password:
Test123!(or stronger) - Realm roles:
admin
- Username:
-
Create
testuseruser:- Username:
testuser - Password:
Test123!(or stronger) - Realm roles:
user(noadminrole)
- Username:
Then run production tests:
E2E_TARGET=production pnpm playwright test tests/e2e-rbac.spec.ts- OIDC.md - How OIDC authentication works in PKI Manager (public clients, PKCE, token validation)
- DEPLOYMENT.md - Docker deployment and E2E testing instructions
- Keycloak Documentation
- OIDC Specification














