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:

  ▸ OpenID4VP overview

  ▸ Response validation and verifiers

  ▸ DCQL queries, user profiles and logging


1) Clients

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.