OpenID4VP / Clients and configuration (v6.6)
Choose how your Java application opens the wallet: a URL or QR code with OpenId4VpClient, or the browser’s Digital Credentials API with OpenId4VpDcApiClient. EudiWalletClient fixes the settings for the EUDI PID profile; the configuration below explains what each choice requires.
See also:
▸ Response validation and verifiers
▸ DCQL queries, user profiles and logging
1) Clients
OpenId4VpClient: the generic verifier, for a wallet invoked by a URL. The redirection is aopenid4vp://URL: on the same device the browser follows it and the wallet opens; on another device, the application must display it as a QR code: it calls the protected URL via AJAX, and the wallet URL is returned in theLocationheader. The wallet then fetches the request object by HTTP (request_uri), or first posts what it supports and gets a request object built for it (request_uri_method=post), and posts its answer (direct_postordirect_post.jwt): all of it on the regular pac4j callback URL, without any session. The page waits for the answer and calls back when it has arrived, which turns the received presentation into aVerifiableCredentialProfile: to know when, it polls an endpoint of the application returningclient.getPresentationStatus(ctx), which isPENDING,RECEIVEDorEXPIREDfor the transaction of the browser sessionOpenId4VpDcApiClient: the same verifier over the Digital Credentials API of the browser, with aOpenId4VpDcApiConfiguration. The page never leaves: the client answers the page a JSON{"request": "<signed request object>"}which the page passes tonavigator.credentials.get(), the browser hands it to the wallet with the authenticated origin of the page, and the page posts the answer back (dc_apiordc_api.jwt). Unlike the URL binding, the wallet never calls the application by itself: every request reaches the application from the browser, with its web session, as any other page request. In return, the request must be signed, and the origins the page runs from must be declaredEudiWalletClient: theOpenId4VpClientwith the HAIP choices pinned and not configurable: thex509_hashprefix, so the certificate loaded from the keystore is the identity of the verifier, thedirect_post.jwtresponse mode, and theEudiPidProfile, whose identifier claim must be chosen by the application. Use it to read the person identification data of an EUDI wallet, the generic client to talk to any other wallet or to another credential.
The following examples configure the presentation request, including the claim identifying the user: a client whose DCQL query cannot return that claim refuses to initialize. See The profile identifier.
Example (EUDI wallet, with the relying party access certificate in a keystore):
OpenId4VpConfiguration config = new OpenId4VpConfiguration()
.setKeystore(new KeystoreProperties()
.setKeystorePath("/path/to/access-certificate.p12")
.setKeystorePassword("...")
.setKeyStoreAlias("rp"))
.setDcqlQuery(EudiPidQuery.sdJwtVc(PERSONAL_ADMINISTRATIVE_NUMBER, GIVEN_NAME, AGE_OVER_18))
// the PID defines no sub: name the claim identifying the user, here one that the targeted PID provider issues
.setProfileIdResolver(ProfileIdResolver.issuerAndClaim(PERSONAL_ADMINISTRATIVE_NUMBER));
EudiWalletClient client = new EudiWalletClient(config);
personal_administrative_number is optional in the PID and not issued by every provider: check it against the target
ecosystem, or set a ProfileIdResolver (see The profile identifier).
Example (any wallet, over the Digital Credentials API, with a DID):
OpenId4VpDcApiConfiguration config = new OpenId4VpDcApiConfiguration();
config.setExpectedOrigins(List.of("https://verifier.example.org"))
.setClientId("did:web:verifier.example.org")
.setClientIdPrefix(ClientIdPrefix.DECENTRALIZED_IDENTIFIER)
.setJwks(new JwksProperties().setJwksPath("/path/to/verifier.jwks").setKid("verifier-key"))
.setDcqlQuery(new DcqlQuery()
.addCredential(new CredentialQuery("badge", CredentialFormat.SD_JWT_VC)
.setVctValues("https://credentials.example.org/employee-badge")
// sub: the claim the default profile definition identifies the user with
.addClaim("sub")
.addClaim("family_name")
.addClaim(new ClaimsQuery("role").withValues("manager"))));
OpenId4VpDcApiClient client = new OpenId4VpDcApiClient(config);
2) The configuration
The OpenId4VpConfiguration has the following properties, with the HAIP choices as defaults:
| Property | Default | Purpose |
|---|---|---|
clientIdPrefix |
X509_HASH |
How the wallet authenticates the verifier: X509_SAN_DNS and X509_HASH by a X.509 certificate chain published in the request object (the trust anchor being removed from it), DECENTRALIZED_IDENTIFIER by a key looked up in the DID document, REDIRECT_URI by nothing at all, for the wallets which accept an unsigned request. The verifier_attestation and openid_federation prefixes are not supported |
clientId |
The identifier of the verifier, without its prefix: a DNS name among the subject alternative names of the certificate for X509_SAN_DNS, the DID for DECENTRALIZED_IDENTIFIER. Computed and not settable for X509_HASH (the hash of the certificate) and REDIRECT_URI (the response URI of each request) |
|
jwks |
A JwksProperties: the JWKS holding the signing key of the request object, with an optional kid to pick it. The key must carry a x5c member for the X.509 prefixes and a kid for the DID |
|
keystore |
A KeystoreProperties: the keystore holding the signing key and its certificate chain, used when no JWKS is defined. The natural source for the X.509 prefixes |
|
requestObjectSigningKey |
Read-only: the key resolved from the two sources above at initialization. Its algorithm is derived from the key itself, so a P-256 key signs with the ES256 that HAIP mandates | |
responseMode |
DIRECT_POST_JWT |
How the wallet returns the presentation: DIRECT_POST posted in clear or DIRECT_POST_JWT posted encrypted to the response URI, DC_API or DC_API_JWT through the browser. The encrypted modes generate an ephemeral ECDH-ES P-256 key for each request and accept A128GCM and A256GCM; a clear mode is announced once with a warning as it does not fit HAIP. A wallet answering in clear a request which asked for encryption is refused; a wallet answering with an error (error, error_description) makes the callback fail with that error |
dcqlQuery |
Required, including when scope is configured. Always used for response validation, and sent to the wallet only when scope is absent or blank. The DCQL query, the only query language of OpenID4VP 1.0: which credentials, of which format, with which claims. A DcqlQuery built programmatically (CredentialQuery, ClaimsQuery, TrustedAuthority, CredentialSetQuery, with EudiPidQuery for the person identification data), or its JSON given as plain text to setDcqlQuery(String). Checked at initialization against the rules of the specification: unique identifiers, sets referencing existing credentials, claim identifiers where claim sets need them |
|
scope |
An alias for a DCQL query, sent instead of it: which aliases exist, and which query each stands for, is defined by an ecosystem, not by the specification, and a wallet may support none. Optional: when non-blank, only this alias is sent to the wallet. dcqlQuery must still be configured with the equivalent query to validate the response |
|
verifierInfo |
empty | Attestations about the verifier (VerifierAttestation: a format, the data, optional credentialIds), sent as the verifier_info parameter: what a third party says this verifier is entitled to ask, such as the registration certificate of an EUDI relying party, which the wallet may show to the End-User or check the request against. The formats belong to the ecosystem; nothing comes back |
credentialVerifiers |
SdJwtVcVerifier |
The interchangeable CredentialVerifier of each credential format (SD_JWT_VC is dc+sd-jwt, MSO_MDOC is mso_mdoc), registered with addCredentialVerifier(verifier). One must be registered for each requested format. The built-in SD-JWT VC verifier requires the optional EUDI dependency and explicit issuer trust configuration; see Configuring credential verifiers. |
transactionLifetimeSeconds |
300 |
How long a request stays valid: stamped as the exp of the request object, and the date at which the pending transaction is dropped from the store |
transactionStore |
VpTransactionStore |
The Store of the pending transactions, keyed by their identifier: the wallet legs carry no session and find the request there. The answer of the wallet is kept apart, under the key <identifier>#response, with the same expiration, so that no other leg can overwrite it. In memory by default; use a shared store (Redis, Hazelcast…) behind several instances |
nonceGenerator |
32 random characters | Generates the nonce sent to the wallet, which the presentation must be bound to |
transactionIdGenerator |
32 random characters | Generates the transaction identifier, visible in the request_uri and in the response URI |
profileIdResolver |
issuerAndClaim("sub"), none for EudiWalletClient |
How the user is identified from the verified credentials, see The profile identifier. Checked against dcqlQuery at initialization |
stateGenerator |
64 random characters | Generates the state sent with every request invoking a wallet by URL (not over the Digital Credentials API) and checked on the response: it is what binds the response to the request when a presentation comes without holder binding |
requestUriMethod |
POST |
How the wallet fetches the request object: GET as RFC 9101 defines, or POST to let it first post its metadata (wallet_metadata, what it supports) and a nonce (wallet_nonce). The request object then carries the nonce back, and publishes only the credential formats and the response encryption algorithms the wallet declared, the request being refused when it declares none of them. Announced in the wallet URL as request_uri_method=post; a wallet which does not support it falls back to a GET, so nothing is lost. Only meaningful for a signed request over the URL binding |
walletScheme |
openid4vp:// |
The custom scheme of the URL invoking a wallet on the same device; a wallet may register another one (eudi-openid4vp:// for the EUDI reference wallet, haip://…) |
The OpenId4VpDcApiConfiguration adds one property and closes two: its default response mode is DC_API_JWT, only DC_API and DC_API_JWT are accepted, and the REDIRECT_URI prefix is refused since this binding hands the browser a signed request.
| Property | Default | Purpose |
|---|---|---|
expectedOrigins |
The origins (scheme, host and optional port, nothing more) the page calling the API runs from. The browser gives the wallet the actual origin of the page, which checks it is one of them: this is what ties a signed request to your site and defeats its replay from another one |
The clients themselves expose a requestObjectBuilder (OpenId4VpRequestObjectBuilder, or DcApiRequestObjectBuilder over the Digital Credentials API), to be overridden to add parameters to the request object, and the usual redirectionActionBuilder, credentialsExtractor, authenticator and profileCreator of an indirect client. The OpenId4VpAuthenticator builds the user profile, with its profileDefinition (OpenId4VpProfileDefinition, or EudiPidProfileDefinition for the EudiWalletClient); the default profile creator returns it, and a custom one may enrich it.