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 issuanceDate is normalized at issuance; the JWT nbf is derived from the normalized value. If a mapper sets a VC expirationDate, it is normalized and emitted as JWT exp.
  • 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 for iat, 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 protocolMapper names 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.

  1. 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.
  1. Import the client using the following curl command:
    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:

  1. 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

Thanks for your feedback.