Keycloak Integration Guide¶
This guide walks you through integrating EUDIPLO with Keycloak using the Chained AS mode. In this setup, EUDIPLO acts as an OAuth Authorization Server facade while Keycloak handles user authentication.
Why Chained AS with Keycloak?¶
Many organizations already use Keycloak for identity management. The Chained AS mode lets you:
- Reuse existing Keycloak users and authentication flows — no need to duplicate identity infrastructure
- Keep session correlation simple — EUDIPLO automatically includes
issuer_statein tokens - Access full user claims in webhooks — ID token and access token claims from Keycloak are passed to your webhook
- Validate wallet attestations — EUDIPLO validates wallet attestations against configured trust lists (not possible with External AS)
- No Keycloak modifications required — unlike External AS mode, you don't need custom token mappers
For the architecture details, see Chained AS.
Prerequisites¶
- A running Keycloak instance (tested with Keycloak 22+)
- EUDIPLO deployed and accessible at a public URL
- A tenant configured in EUDIPLO
Step 1: Configure Keycloak¶
Create a Realm (or use existing)¶
If you don't have a realm yet:
- Log in to Keycloak Admin Console
- Click Create realm
- Enter a name (e.g.,
eudiplo) and click Create
Create a Client for EUDIPLO¶
- Go to Clients → Create client
- Configure the client:
| Setting | Value |
|---|---|
| Client type | OpenID Connect |
| Client ID | eudiplo-chained-as |
| Client authentication | On (confidential client) |
| Valid redirect URIs | https://your-eudiplo-url/*/chained-as/callback |
Redirect URI Pattern
Use * as a wildcard for the tenant name, or specify exact tenant names like https://eudiplo.example.com/prod/chained-as/callback.
- Click Save
- Go to the Credentials tab and copy the Client secret
Configure Scopes¶
Ensure the following scopes are available (they're defaults in Keycloak):
openid— Required for OIDCprofile— Includes name, preferred_usernameemail— Includes email address
To add custom claims (e.g., employee ID), create a custom scope with a mapper.
Step 2: Configure EUDIPLO¶
Update Issuance Configuration¶
Add the chainedAs section to your issuance configuration:
{
"display": [
{
"name": "My Issuer",
"locale": "en"
}
],
"chainedAs": {
"enabled": true,
"upstream": {
"issuer": "https://keycloak.example.com/realms/eudiplo",
"clientId": "eudiplo-chained-as",
"clientSecret": "paste-your-client-secret-here",
"scopes": ["openid", "profile", "email"]
},
"token": {
"lifetimeSeconds": 3600
},
"requireDPoP": false
}
}
| Field | Description |
|---|---|
upstream.issuer |
Your Keycloak realm URL (must end with /realms/{realm-name}) |
upstream.clientId |
The client ID you created in Keycloak |
upstream.clientSecret |
The client secret from Keycloak's Credentials tab |
upstream.scopes |
Scopes to request from Keycloak |
Configure Claims Webhook¶
To use the authenticated user's claims, configure a webhook on your credential configuration:
{
"credentialConfigurationId": "EmployeeBadge",
"claimsWebhook": {
"url": "https://your-backend.example.com/claims",
"auth": {
"type": "apiKey",
"config": {
"headerName": "X-API-Key",
"value": "your-secret-key"
}
}
}
}
Your webhook will receive the Keycloak user's claims in the identity object:
{
"session": "abc123",
"credential_configuration_id": "EmployeeBadge",
"identity": {
"iss": "https://keycloak.example.com/realms/eudiplo",
"sub": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"token_claims": {
"email": "john.doe@example.com",
"email_verified": true,
"preferred_username": "jdoe",
"given_name": "John",
"family_name": "Doe"
}
}
}
Step 3: Create a Credential Offer¶
Create an offer using the authorization code grant:
curl -X POST https://eudiplo.example.com/api/offers \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: prod" \
-d '{
"credentialConfigurationId": "EmployeeBadge",
"grant": "authorization_code"
}'
The wallet will:
- Receive the credential offer
- Discover the Chained AS metadata from EUDIPLO
- Redirect the user to EUDIPLO's
/authorizeendpoint - EUDIPLO redirects to Keycloak for login
- After login, Keycloak redirects back to EUDIPLO
- EUDIPLO issues an access token to the wallet
- The wallet requests the credential using that token
Step 4: Verify the Integration¶
Check Chained AS Metadata¶
Should return metadata including the authorization and token endpoints.
Check JWKS¶
Should return the public keys used to sign access tokens.
Test with a Wallet¶
- Create a credential offer
- Scan or click the offer in a compatible wallet
- You should be redirected to Keycloak's login page
- After login, the credential should be issued
Troubleshooting¶
"Invalid redirect URI" in Keycloak¶
Cause: The redirect URI doesn't match what's configured in Keycloak.
Fix: Ensure the Valid redirect URIs in your Keycloak client includes:
"Error during verification of access token jwt"¶
Cause: Token verification failed, often because the JWKS doesn't include the kid.
Fix: Ensure EUDIPLO is using a recent version with the kid resolution fix. Check that:
Returns a non-null key ID.
Webhook doesn't receive user claims¶
Cause: The Chained AS session wasn't found or claims weren't stored.
Fix:
- Check EUDIPLO logs for "Stored upstream identity claims"
- Verify the
issuer_stateclaim is in the access token - Ensure the credential request uses the Chained AS token (not an external token)
User claims are empty in webhook¶
Cause: Keycloak scopes don't include the claims you need.
Fix:
- In Keycloak, go to Client scopes and verify
profileandemailare included - Check that the client has these scopes assigned (Client → Client scopes tab)
- For custom claims, create a custom scope with a token mapper
Advanced: Adding Custom Claims¶
To pass custom user attributes (e.g., employee ID) to EUDIPLO:
In Keycloak¶
- Go to Client scopes → Create client scope
- Name it (e.g.,
employee) - Go to Mappers → Add mapper → By configuration → User Attribute
- Configure:
- Name:
employee_id - User Attribute:
employee_id - Token Claim Name:
employee_id - Add to ID token: On
- Add to access token: On
- Name:
- Go to your client → Client scopes → Add client scope → Select
employee
In EUDIPLO¶
Add the scope to your issuance configuration:
The employee_id claim will now appear in your webhook's token_claims.