Keycloak provides experimental support for OpenID for Verifiable Credential Issuance.
Introduction
This chapter provides step-by-step instructions for configuring Keycloak as a Verifiable Credential Issuer using the OpenID for Verifiable Credential Issuance (OID4VCI) protocol. It outlines the process for setting up a Keycloak instance to securely issue and manage Verifiable Credentials (VCs), supporting decentralized identity solutions.
What are Verifiable Credentials (VCs)?
Verifiable Credentials (VCs) are cryptographically signed, tamper-evident data structures that represent claims about an entity, such as a person, organization, or device. They are foundational to decentralized identity systems, allowing secure and privacy-preserving identity verification without reliance on centralized authorities. VCs support advanced features like selective disclosure and zero-knowledge proofs, enhancing user privacy and security.
What is OID4VCI?
OpenID for Verifiable Credential Issuance (OID4VCI) is an extension of the OpenID Connect (OIDC) protocol. It defines a standardized, interoperable framework for credential issuers to deliver VCs to holders, who can then present them to verifiers. OID4VCI leverages Keycloak’s existing authentication and authorization capabilities to streamline VC issuance.
Scope of This Chapter
This chapter covers the following technical configurations:
- Creating a dedicated realm for VC issuance.
- Setting up a test user for credential testing.
- Configuring custom cryptographic keys for signing and encrypting VCs.
- Defining realm attributes to specify VC metadata.
- Establishing client scopes and mappers to include user attributes in VCs.
- Registering a client to handle VC requests.
- Verifying the configuration using the issuer metadata endpoint.
Prerequisites
Ensure the following requirements are met before configuring Keycloak as a Verifiable Credential Issuer:
Keycloak Instance
A running Keycloak server with the OID4VCI feature enabled.
To enable the feature, add the following flag to the startup command:
--features=oid4vc-vci
Verify activation by checking the server logs for the OID4VC_VCI initialization message.
Configuring Credential Issuance in Keycloak
In Keycloak, Verifiable Credentials are managed through ClientScopes, with each ClientScope representing a single Verifiable Credential type. To enable the issuance of a credential, the corresponding ClientScope must be assigned to an OpenID Connect client – ideally as optional.
During the OAuth2 authorization process, the credential-specific scope can be requested by including the ClientScope’s name in the scope parameter of the authorization request. Once the user has successfully authenticated, the resulting Access Token MUST include the requested ClientScope in its scope claim. To ensure this, make sure the ClientScope option Include in token scope is enabled.
With this Access Token, the Verifiable Credential can be issued at the Credential Endpoint.
Authentication
An access token is required to authenticate API requests.
Refer to the following Keycloak documentation sections for detailed steps on:
- Creating a Client
- Obtaining an Access Token
Configuration Steps
Follow these steps to configure Keycloak as a Verifiable Credential Issuer. Each section is detailed with procedures, explanations, and examples where applicable.
Creating a Realm
A realm in Keycloak is a logical container that manages users, clients, roles, and authentication flows. For Verifiable Credential (VC) issuance, create a dedicated realm to ensure isolation and maintain a clear separation of functionality.
Creating a User Account
A test user is required to simulate credential issuance and verify the setup.
Ensure that the user has a valid username, email, and password. If the password should not be reset upon first login, disable the “Temporary” toggle during password configuration.
Key Management Configuration
Keycloak uses cryptographic keys for signing and encrypting Verifiable Credentials (VCs). To ensure secure and standards-compliant issuance, configure ECDSA (ES256) for signing, RSA (RS256) for signing, and RSA-OAEP for encryption using a keystore.
Configuring Key Providers
To enable cryptographic operations for VC issuance:
- ECDSA (ES256) Key: Used for signing VCs with the ES256 algorithm.
- RSA (RS256) Key: Alternative signing mechanism using RS256.
- RSA-OAEP Key: Used for encrypting sensitive data in VCs.
Each key must be registered as a java-keystore provider within the Realm Settings > Keys section, ensuring: – The keystore file is correctly specified and securely stored. – The appropriate algorithm (ES256, RS256, or RSA-OAEP) is selected. – The key is active, enabled, and configured with the correct usage (signing or encryption). – Priority values are set to define precedence among keys.
Registering Realm Attributes
Realm attributes define metadata for Verifiable Credentials (VCs), such as expiration times, supported formats, and scope definitions. These attributes allow Keycloak to issue VCs with predefined settings.
Since the Keycloak Admin Console does not support direct attribute creation, use the Keycloak Admin REST API to configure these attributes.
Define Realm Attributes
Create a JSON file (e.g., realm-attributes.json) with the following content:
{
"realm": "oid4vc-vci",
"enabled": true,
"attributes": {
"preAuthorizedCodeLifespanS": 120
}
}
Attribute Breakdown
The attributes section contains issuer-specific metadata: – preAuthorizedCodeLifespanS – Defines how long pre-authorized codes remain valid (in seconds). – oid4vc.attestation.trusted_keys – JSON array of trusted JWK (JSON Web Key) objects for attestation proof validation. Each JWK must include a kid (key ID) field. These keys take precedence over realm session keys when there are conflicts. Useful for configuring additional trusted keys beyond the realm’s default keys. Format: JSON array of JWK objects, e.g., [{"kid":"key1","kty":"EC",…},{"kid":"key2","kty":"RSA",…}]. – oid4vc.attestation.trusted_key_ids – Comma-separated list of key IDs from the realm’s key providers to use for attestation proof validation. Keys are looked up by their kid regardless of enabled status, allowing the use of disabled keys that are not exposed in well-known endpoints. This attribute takes the highest priority when merging trusted keys. Format: comma-separated list of key IDs, e.g., key-id-1,key-id-2,key-id-3.
Import Realm Attributes
Use the following curl command to import the attributes into Keycloak:
curl -X PUT "https://localhost:8443/admin/realms/oid4vc-vci" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d @realm-attributes.json
Time-claim correlation mitigation
To reduce unintended correlation across multiple issuances or presentations, you can normalize time-related claims by either randomizing them within a time window or rounding them to a coarse time unit. This behavior is opt-in and controlled by the following realm attributes:
| Attribute | Default | Description |
|---|---|---|
oid4vci.time.claims.strategy |
off |
Strategy to apply to time claims. Supported values: off, randomize, round. |
oid4vci.time.randomize.window.seconds |
86400 |
When strategy is randomize, subtract a random number of seconds between 0 and the value of this attribute from the original timestamp to mitigate correlation attacks. |
oid4vci.time.round.unit |
SECOND |
When strategy is round, truncate timestamps to the selected unit boundary (UTC). Supported values: SECOND, MINUTE, HOUR, DAY. |
How it is applied during issuance:
- For JWT-VC, the credential
issuanceDateis normalized at issuance; the JWTnbfis derived from the normalized value. If a mapper sets a VCexpirationDate, it is normalized and emitted as JWTexp.
- For SD-JWT VCs, time-related claims (
iat,nbf,exp) are typically set using protocol mappers. Use the available OID4VC mappers, such as the Issued At Time Claim Mapper foriat, to populate these claims. Values are automatically normalized according to the realm strategy.
Examples:
# Round to start of day (UTC)
curl -X PUT "https://localhost:8443/admin/realms/oid4vc-vci" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"attributes": {
"oid4vci.time.claims.strategy": "round",
"oid4vci.time.round.unit": "DAY"
}
}'
# Randomize within the last 24 hours
curl -X PUT "https://localhost:8443/admin/realms/oid4vc-vci" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"attributes": {
"oid4vci.time.claims.strategy": "randomize",
"oid4vci.time.randomize.window.seconds": "86400"
}
}'
Create Client Scopes with Mappers
Client scopes define which user attributes are included in Verifiable Credentials (VCs). Therefore, they are considered the Verifiable Credential configuration itself. These scopes use protocol mappers to map specific claims into VCs and the protocol mappers will also contain the corresponding metadata for claims that is displayed at the Credential Issuer Metadata Endpoint.
You can create the ClientScopes using the Keycloak web Administration Console, but the web Administration Console does not yet support adding metadata configuration. For metadata configuration, you will need to use the Admin REST API.
Define a Client Scope with a Mapper
Create a JSON file (e.g., client-scopes.json) with the following content:
{
"name": "vc-scope-mapping",
"protocol": "oid4vc",
"attributes": {
"include.in.token.scope": "true",
"vc.issuer_did": "did:web:vc.example.com",
"vc.credential_configuration_id": "my-credential-configuration-id",
"vc.credential_identifier": "my-credential-identifier",
"vc.format": "jwt_vc",
"vc.expiry_in_seconds": 31536000,
"vc.verifiable_credential_type": "my-vct",
"vc.supported_credential_types": "credential-type-1,credential-type-2",
"vc.credential_contexts": "context-1,context-2",
"vc.credential_signing_alg": "ES256",
"vc.cryptographic_binding_methods_supported": "jwk",
"vc.signing_key_id": "key-id-123456",
"vc.display": "[{\"name\": \"IdentityCredential\", \"logo\": {\"uri\": \"https://university.example.edu/public/logo.png\", \"alt_text\": \"a square logo of a university\"}, \"locale\": \"en-US\", \"background_color\": \"#12107c\", \"text_color\": \"#FFFFFF\"}]",
"vc.sd_jwt.number_of_decoys": "2",
"vc.credential_build_config.sd_jwt.visible_claims": "iat,jti,nbf,exp,given_name",
"vc.credential_build_config.hash_algorithm": "SHA-256",
"vc.credential_build_config.token_jws_type": "JWS",
"vc.include_in_metadata": "true"
},
"protocolMappers": [
{
"name": "academic_title-mapper-bsk",
"protocol": "oid4vc",
"protocolMapper": "oid4vc-static-claim-mapper",
"config": {
"claim.name": "academic_title",
"staticValue": "N/A"
}
},
{
"name": "givenName",
"protocol": "oid4vc",
"protocolMapper": "oid4vc-user-attribute-mapper",
"config": {
"claim.name": "given_name",
"userAttribute": "firstName",
"vc.mandatory": "false",
"vc.display": "[{\"name\": \"الاسم الشخصي\", \"locale\": \"ar-SA\"}, {\"name\": \"Vorname\", \"locale\": \"de-DE\"}, {\"name\": \"Given Name\", \"locale\": \"en-US\"}, {\"name\": \"Nombre\", \"locale\": \"es-ES\"}, {\"name\": \"نام\", \"locale\": \"fa-IR\"}, {\"name\": \"Etunimi\", \"locale\": \"fi-FI\"}, {\"name\": \"Prénom\", \"locale\": \"fr-FR\"}, {\"name\": \"पहचानी गई नाम\", \"locale\": \"hi-IN\"}, {\"name\": \"Nome\", \"locale\": \"it-IT\"}, {\"name\": \"名\", \"locale\": \"ja-JP\"}, {\"name\": \"Овог нэр\", \"locale\": \"mn-MN\"}, {\"name\": \"Voornaam\", \"locale\": \"nl-NL\"}, {\"name\": \"Nome Próprio\", \"locale\": \"pt-PT\"}, {\"name\": \"Förnamn\", \"locale\": \"sv-SE\"}, {\"name\": \"مسلمان نام\", \"locale\": \"ur-PK\"}]"
}
}
]
}
From the example above:
- It is important to set
include.in.token.scope=true, see Attribute table: include.in.token.scope.
- Most of the named attributes above are optional. See below: Attribute Breakdown.
- You can determine the appropriate
protocolMappernames by first creating them through the Web Administration Console and then retrieving their definitions via the Admin REST API.
Attribute Breakdown – ClientScope
| Property | Required | Description / Default |
|---|---|---|
name |
required | Name of the client scope. |
protocol |
required | Protocol used by the client scope. Use oid4vc for OpenID for Verifiable Credential Issuance, which is an OAuth2 extension (like openid-connect). |
include.in.token.scope |
required | This value MUST be true. It ensures that the scope’s name is included in the scope claim of the issued Access Token. |
protocolMappers |
optional | Defines how claims are mapped into the credential and how metadata is exposed via the issuer’s metadata endpoint. |
vc.issuer_did |
optional | The Decentralized Identifier (DID) of the issuer. Default: $ |
vc.credential_configuration_id |
optional | The credentials configuration ID. Default: $+ |
vc.credential_identifier |
optional | The credentials identifier. Default: $+ |
vc.format |
optional | Defines the VC format (e.g., jwt_vc). Default: dc+sd-jwt |
vc.verifiable_credential_type |
optional | The Verifiable Credential Type (VCT). Default: $+ |
vc.supported_credential_types |
optional | The type values of the Verifiable Credential Type. Default: $+ |
vc.credential_contexts |
optional | The context values of the Verifiable Credential Type. Default: $+ |
vc.credential_signing_alg |
optional | Supported signature algorithm for this credential. Default: All asymmetric signing algorithms backed by realm keys. |
vc.cryptographic_binding_methods_supported |
optional | Supported cryptographic methods (if applicable). Default: jwk |
vc.signing_key_id |
optional | The ID of the key to sign this credential. Default: none |
vc.display |
optional | Display information shown in the user’s wallet about the issued credential. Default: none |
vc.sd_jwt.number_of_decoys |
optional | Used only with format dc+sd-jwt. Number of decoy hashes in the SD-JWT. Default: 10 |
vc.credential_build_config.sd_jwt.visible_claims |
optional | Used only with format dc+sd-jwt. Claims always disclosed in the SD-JWT body. Default: id,iat,nbf,exp,jti |
vc.credential_build_config.hash_algorithm |
optional | Hash algorithm used before signing the credential. Default: SHA-256 |
vc.credential_build_config.token_jws_type |
optional | JWT type written into the typ header of the token. Default: JWS |
vc.expiry_in_s |
optional | Credential expiration time in seconds. Default: 31536000 (one year) |
vc.include_in_metadata |
optional | If this claim should be listed in the credentials metadata. Default: true but depends on the mapper-type. Claims like jti, nbf, exp, etc. are set to false by default. |
vc.key_attestations_required |
optional | Indicates whether issuing this credential requires a key attestation. Default: false. |
vc.key_attestations_required.key_storage |
optional | Comma separated list of accepted key-storage attack potential levels (see ISO 18045 levels, e.g. iso_18045_high). Only relevant if vc.key_attestations_required is present. Default: none |
vc.key_attestations_required.user_authentication |
optional | Comma separated list of accepted user-authentication attack potential levels (see ISO 18045 levels). Only relevant if vc.key_attestations_required is present. Default: none |
Attribute Breakdown – ProtocolMappers
- name – Mapper identifier.
- protocol – Must be oid4vc for Verifiable Credentials.
- protocolMapper – Specifies the claim mapping strategy (e.g., oid4vc-static-claim-mapper).
- config: contains the protocol-mappers specific attributes.
Most claims are dependent on the protocolMapper-value, but there are also commonly used claims available for all ProtocolMappers:
| Property | Required | Description / Default |
|---|---|---|
claim.name |
required | The name of the attribute that will be added into the Verifiable Credential. Default: none |
userAttribute |
required | The name of the users-attribute that will be used to map the value into the claim.name of the Verifiable Credential. Default: none |
vc.mandatory |
optional | If the credential must be issued with this claim. Default: false |
vc.display |
optional | Metadata information that is displayed at the credential-issuer metadata-endpoint. Default: none |
Import the Client Scope
Use the following curl command to import the client scope into Keycloak:
curl -X POST "https://localhost:8443/admin/realms/oid4vc-vci/client-scopes" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d @client-scopes.json
Create the Client
Set up a client to handle Verifiable Credential (VC) requests and assign the necessary scopes. The client does not differ from regular OpenID Connect clients — with one exception: it must have the appropriate optional ClientScopes assigned that define the Verifiable Credentials it is allowed to issue.
- Create a JSON file (e.g.,
oid4vc-rest-api-client.json) with the following content:
{ "clientId": "oid4vc-rest-api", "enabled": true, "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": true, "clientAuthenticatorType": "client-secret", "redirectUris": ["http://localhost:8080/*"], "directAccessGrantsEnabled": true, "defaultClientScopes": ["profile"], "optionalClientScopes": ["vc-scope-mapping"], "attributes": { "client.secret.creation.time": "1719785014", "client.introspection.response.allow.jwt.claim.enabled": "false", "login_theme": "keycloak", "post.logout.redirect.uris": "http://localhost:8080" } }- clientId: Unique identifier for the client.
- optionalClientScopes: Links the vc-scope-mapping scope for VC requests.
- Import the client using the following
curlcommand:
curl -k -X POST "https://localhost:8443/admin/realms/oid4vc-vci/clients" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d @oid4vc-rest-api-client.json
Verify the Configuration
Validate the setup by accessing the issuer metadata endpoint:
- Open a browser or use a tool like curl to visit:
https://localhost:8443/.well-known/openid-credential-issuer/realms/oid4vc-vci
A successful response returns a JSON object containing details such as: – Supported claims – Credential formats – Issuer metadata
Conclusion
You have successfully configured Keycloak as a Verifiable Credential Issuer using the OID4VCI protocol*. This setup leverages Keycloak’s robust identity management capabilities to issue secure, standards-compliant VCs.
For a complete reference implementation, see the sample project: Keycloak SSI Deployment.
Haben Sie noch weitere Fragen?
Visit the Support Portal


