Important! Before you proceed, please read the EUDI Wallet Reference Implementation project description
- Disclaimer
- Overview
- What’s in the box
- Stack / Tooling
- Requirements
- Setup and Build
- Deploy to Keycloak
- Entry points (SPIs)
- How to enable WalletStatusMapper
- Usage
- Client Authenticator
- Gradle Tasks / Scripts
- Environment and Configuration
- Project Structure
- How to contribute
- License
Important
This experimental Keycloak extension is implemented to serve the needs of the EUDI project only.
It is created strictly for testing and development purposes and to enable the reference implementation's kotlin issuer to achieve:
- Compliance with ARF's technical spcification TS3
- Compliance with ETSI TS 119 472-3 V1.1.1 (2026-03)
- Compliance with a trust model based on Lists of Trusted Entities as defined in ETSI TS 119 602 V1.1.1 (2025-11) and Lists of Trusted Lists as defined in ETSI TS 119 612 V2.4.1 (2025-08)
This is by no means:
This project provides the following Keycloak Providers implementing parts of the OAuth 2.0 Attestation‑Based Client Authentication draft (draft‑ietf‑oauth‑attestation‑based‑client‑auth‑07):
- A handler (Provider) for generating and validating short-lived cryptographic challenges.
- A realm resource that issues short‑lived cryptographic challenges.
- A Client Authenticator that authenticates clients using Client Attestation and Client Attestation PoP HTTP Headers.
- A Well‑Known Provider that augments the OIDC discovery document with ABCA metadata (challenge endpoint URL and supported algorithms).
- A Protocol Mapper that maps the
client_statusclaim of the Client Attestation to the Access Token, Access Token Response, or Introspection Endpoint Response.
- Challenge handler: Provider + ProviderFactory + SPI (registered via META‑INF/services).
- Challenge endpoint: RealmResource + RealmResourceProvider + RealmResourceProviderFactory (registered via META‑INF/services).
- Client authenticator: ClientAuthenticator + ClientAuthenticatorFactory (registered via META‑INF/services); Parses OAuth‑Client‑Attestation and OAuth‑Client‑Attestation‑PoP HTTP Headers, verifies the Client Attestation issuer is trusted, validates Client Attestation PoP signature, validates Challenge.
- Well‑Known augmentation: WellKnownProvider + WellKnownProviderFactory (registered via META‑INF/services); Adds
challenge_endpoint,client_attestation_signing_alg_values_supported, andclient_attestation_pop_signing_alg_values_supportedto the OIDC discovery document. - Client Status Protocol Mapper: ProtocolMapper (registered via META‑INF/services); Adds
client_statusto Access Token, Access Token Response, Introspection Endpoint Response based on its configuration.
- Language: Kotlin (JVM)
- Runtime target: Java 21
- Identity platform: Keycloak 26.6.2
- Build: Gradle (Kotlin DSL) with Gradle Version Catalog
- Lint/format: Spotless + ktlint
- JWT/JWK: Nimbus JOSE + JWT
- JDK 21 (Adoptium/Temurin recommended; build config sets vendor = ADOPTIUM)
- Gradle wrapper (included): use ./gradlew.bat on Windows
- Keycloak 26.6.2 server to deploy the resulting provider JAR(s)
- Format (optional) and build (Windows PowerShell):
./gradlew.bat spotlessApply build
- The build task creates an artifacts that contains all its dependencies shaded into a single JAR:
build\libs\<name>-<version>.jar
- Copy either the generated JAR into your Keycloak
providersdirectory. Examples:- Linux container: /opt/keycloak/providers
- Windows distribution: <keycloak_install>\providers
- Restart Keycloak.
- Challenge handler:
- src\main\resources\META-INF\services\org.keycloak.provider.Spi -> eu.europa.ec.eudi.keycloak.ext.abca.challenge.ChallengeHandlerSpi
- src\main\resources\META-INF\services\eu.europa.ec.eudi.keycloak.ext.abca.challenge.ChallengeHandlerProviderFactory -> eu.europa.ec.eudi.keycloak.ext.abca.challenge.DefaultChallengeHandlerProviderFactory
- Realm Resource: src\main\resources\META-INF\services\org.keycloak.services.resource.RealmResourceProviderFactory -> eu.europa.ec.eudi.keycloak.ext.abca.challenge.ChallengeRealmResourceProviderFactory
- Client Authenticator: src\main\resources\META-INF\services\org.keycloak.authentication.ClientAuthenticatorFactory -> eu.europa.ec.eudi.keycloak.ext.abca.authenticator.AttestationBasedClientAuthenticatorFactory
- Well‑Known Provider:
- src\main\resources\META-INF\services\org.keycloak.wellknown.WellKnownProviderFactory -> eu.europa.ec.eudi.keycloak.ext.abca.wellknown.OIDCWellKnownProviderFactory
- src\main\resources\META-INF\services\org.keycloak.wellknown.WellKnownProviderFactory -> eu.europa.ec.eudi.keycloak.ext.abca.wellknown.OAuth2WellKnownProviderFactory
- Client Status Protocol Mapper: src\main\resources\META-INF\services\org.keycloak.protocol.ProtocolMapper -> eu.europa.ec.eudi.keycloak.ext.abca.walletinstanceattestation.ClientStatusProtocolMapper
- Go to the desired client from the
Clients - In
Client scopeschose the desired client scope ex:eudiw-abca-dedicated - Choose
Add mapperand selectBy Configuration - Select
Client Status Protocol Mapperand give it a name ex:client_status - Configure the mapper
- Click
Save
- Well‑known discovery augmentation:
- The OIDC discovery document will include:
challenge_endpoint: URL of the challenge endpoint (i.e.: {baseUrl}/realms/{realm}/challenge)client_attestation_signing_alg_values_supported: [ES256, ES384, ES512]client_attestation_pop_signing_alg_values_supported: [ES256, ES384, ES512]
- The OIDC discovery document will include:
- Challenge endpoint URL:
- Available at: https:///realms//challenge
- Example request:
- POST https:///realms//challenge
- Current response (per code):
{
"attestation_challenge": "value"
}
- Notes:
- Response headers set Cache-Control: no-store, no-cache and Pragma: no-cache.
- Notes:
- Headers expected by the authenticator (per draft):
OAuth-Client-Attestation: a signed JWT containing client metadata and a cnf.jwk binding keyOAuth-Client-Attestation-PoP: a PoP JWT signed by thecnf.jwkkey
- Processing flow (high level):
- Parses Client Attestation JWT; extracts sub (client_id) and cnf.jwk.
- Verify the Client Attestation JWT signature using the key of the first certificate in the x5c claim.
- Verify the Issuer of the Client Attestation JWT is trusted.
- Verifies the PoP JWT signature with the public key from cnf.jwk.
- Verifies
client_statusis valid. - Emits Keycloak events and returns OAuth error invalid_client_attestation on failures.
This KeyCloak extension optionally integrates with eudi-srv-trust-validator to check whether the Issuer of a Client Attestation JWT is trusted or not.
The URL of the Trust Validator Service can be configured using the Admin UI with the Trust Validator Service URL property either at the
Authentication Flow level, or the OAuth 2.0 Client level.
Caution
When no URL is configured, all Client Attestation JWT Issuers are trusted!
- ./gradlew.bat build — Compiles and packages the provider JARs (both Regular and Fat/uber JAR).
- ./gradlew.bat test — Runs tests (JUnit Platform).
- ./gradlew.bat spotlessApply — Applies ktlint formatting via Spotless.
The following keycloak.conf properties (or the equivalent command line arguments, or environment variables) can be used to configure the providers:
Property: spi-challenge-handler--default--bytes
Description: The number of bytes a Challenge is composed of.
Format: Unsigned Integer.
Minimum value: 32
Default value: 64
Property: spi-challenge-handler--default--lifespan
Description: Duration a Challenge is valid for.
Format: ISO 8601 formatted Duration.
Maximum value: PT15M
Default value: PT5M
We welcome contributions to this project. To ensure that the process is smooth for everyone involved, follow the guidelines found in CONTRIBUTING.md.
Copyright (c) 2023-2026 European Commission
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.