Skip to main content

OpenID4VCI and OpenID4VP

A wallet uses two protocols from the OpenID for Verifiable Credentials family:

  • OpenID for Verifiable Credential Issuance (OpenID4VCI) to receive credentials from issuers
  • OpenID for Verifiable Presentations (OpenID4VP) to present credentials to verifiers

To do both, configure and enable at least one instance of each: an OpenID4VCI instance in issuanceProtocol and an OpenID4VP instance in verificationProtocol. This page explains how to configure them for a wallet.

Example configuration​

issuanceProtocol:
OPENID4VCI_FINAL1:
display: "exchange.openIdFinal1"
order: 1
type: "OPENID4VCI_FINAL1"
params:
private:
encryption: "<32-byte hex>"
nonce:
signingKey: "<32-byte hex>"
verificationProtocol:
OPENID4VP_FINAL1:
display: "exchange.openid4vpFinal1"
order: 1
type: "OPENID4VP_FINAL1"
params:
public:
holder:
supportedClientIdSchemes:
[
redirect_uri,
verifier_attestation,
decentralized_identifier,
x509_san_dns,
x509_hash,
]

The sections below explain each part of this configuration, along with optional features such as DPoP, the Authorization Code Flow, and transaction data.

Receiving credentials with OpenID4VCI​

Enable OpenID4VCI​

Configure at least one enabled instance of type OPENID4VCI_FINAL1 in the issuanceProtocol object. See the issuance protocols reference for all options.

Keys​

Each OpenID4VCI instance needs its own keys:

  • encryption: Encrypts the access and refresh tokens the wallet receives from issuers. If you change this key, stored tokens can no longer be used. Credentials already held continue to work, but they can no longer be refreshed and new batches cannot be requested.
  • nonce.signingKey: Signs nonces when Core issues credentials. Required for every instance, even if you only use Core as a wallet.

Keys are 32-byte hex strings. Supply them as environment variables rather than in configuration files. See Secrets and Keys for details.

DPoP​

Some issuers require Demonstrating Proof of Possession (DPoP), which binds the tokens they issue to a key held by the wallet, so a stolen token cannot be used by anyone else. The wallet binds both the access token and, in the Authorization Code Flow, the authorization code itself, so a captured code cannot be redeemed with a different key.

To support DPoP, set holderDpopKeyStorage to the name of a configured key storage provider. The wallet uses that provider to generate a DPoP key, then signs its DPoP proofs with that key.

issuanceProtocol:
OPENID4VCI_FINAL1:
params:
public:
holderDpopKeyStorage: "<key storage instance>"

If holderDpopKeyStorage is not configured, the wallet does not support DPoP.

Even when it is configured, the wallet can only use DPoP with an issuer if both of the following are true:

  • At least one key algorithm the issuer accepts for DPoP is enabled in the wallet's key algorithm configuration.
  • The selected key storage provider can generate a key with that algorithm.

If either is not the case, the wallet logs a warning and continues without DPoP. Whether issuance still succeeds depends on the issuer: issuers that require DPoP reject the request, while others issue without it.

Authorization Code Flow​

When an issuer offers credentials using the Authorization Code Flow, the wallet identifies itself to the issuer with a client ID. Add an entry to the root-level credentialIssuer object for each issuer you want to accept these offers from, with the client ID the wallet should use:

credentialIssuer:
EUDI_PID_FLOW:
display:
en: "EUDI Issuer"
de: "EUDI Issuer"
order: 1
enabled: true
params:
public:
logo: "<logo>"
issuer: "https://issuer.eudiw.dev"
private:
clientId: "my-wallet-id"

URL scheme​

By default, the wallet accepts credential offers using the openid-credential-offer URL scheme, as specified in the OpenID4VCI standard.

To accept offers using another scheme, configure an additional OpenID4VCI instance and set its urlScheme parameter. Any scheme can be used. When the wallet receives a credential offer, it uses the instance whose scheme matches the offer URL.

HAIP​

The OpenID4VC High Assurance Interoperability Profile (HAIP) defines the haip-vci scheme for credential offers. To accept HAIP offers, configure an instance with that scheme:

issuanceProtocol:
OPENID4VCI_FINAL1_HAIP:
display: "exchange.openIdFinal1Haip"
order: 2
type: "OPENID4VCI_FINAL1"
params:
public:
urlScheme: haip-vci
warning

Setting urlScheme to haip-vci does not make an instance HAIP-compliant, and the system does not check the rest of the instance's configuration against HAIP. Configure the instance to meet HAIP's requirements yourself. For example, HAIP requires DPoP support; see DPoP.

Rejecting credential offers​

When a holder rejects a credential offer, the wallet can generate a temporary identifier for the rejection. To enable this, set both:

  • rejectionIdentifier.didMethod: The DID method to use, by configuration instance name.
  • rejectionIdentifier.keyAlgorithm: The key algorithm to use, by configuration instance name, for example ECDSA.

Presenting credentials with OpenID4VP​

Enable OpenID4VP​

Configure at least one enabled instance of type OPENID4VP_FINAL1 in the verificationProtocol object. See the verification protocols reference for all options.

URL scheme​

Verifiers share proof requests as URLs, and the URL scheme signals which flavor of OpenID4VP the request uses. When the wallet handles an invitation URL, it matches the URL's scheme against the urlScheme of its OpenID4VP instances and uses the matching instance. OPENID4VP_FINAL1 instances use openid4vp by default.

To accept requests that use another scheme, configure an additional instance with that urlScheme. Any scheme can be used. See Presenting ISO mdocs for an example.

HAIP​

HAIP defines the haip-vp scheme for proof requests. To accept HAIP requests, configure an instance with that scheme:

verificationProtocol:
OPENID4VP_FINAL1_HAIP:
display: "exchange.openid4vpFinal1Haip"
order: 3
type: "OPENID4VP_FINAL1"
params:
public:
urlScheme: haip-vp
holder:
supportedClientIdSchemes: [x509_hash]
warning

Setting urlScheme to haip-vp does not make an instance HAIP-compliant, and the system does not check the rest of the instance's configuration against HAIP. Configure the instance to meet HAIP's requirements yourself. For example, HAIP requires signed requests to use the x509_hash Client Identifier Scheme.

Client Identifier Schemes​

Client Identifier Schemes (called "Client Identifier Prefixes" in OpenID4VP 1.0) define how the wallet validates the identity of a verifier. When a verifier sends a proof request, its scheme determines how the wallet checks that the request is legitimate.

Schemes provide different levels of trust assurance. For example, redirect_uri provides no cryptographic verification of the verifier's identity, while x509_san_dns requires the verifier to prove control of a domain through certificate validation.

Use holder.supportedClientIdSchemes to set which schemes the wallet accepts. The wallet rejects proof requests that use any other scheme.

For OPENID4VP_FINAL1, use decentralized_identifier for DID-based verifiers. It replaces the did scheme used in earlier drafts.

note

For all supported schemes, see the verification protocols reference.

Transaction data​

OpenID4VP lets a verifier attach transaction_data to a proof request: details of a transaction, such as a document to sign or a payment to confirm, that the holder must review and approve before presenting a credential. The holder's approval is then bound to the presentation.

To handle these requests, configure at least one provider in the root-level transactionDataProvider object. When a request arrives, the wallet uses the provider that supports the request's transaction data type, and fails the invitation if none does. For the supported provider types, see the configuration reference. For the full holder flow, see Transaction Authorization for Wallets.

Display parameters​

Every provider requires transactionDataDisplayParams, which controls how transaction data is turned into human-readable blocks for the holder to review. The wallet returns these blocks as transactionDataDisplay when you fetch a transaction data entry.

  • groupPath: JSONPath selecting what to render as separate blocks. An array produces one block per element; an object or the root $ produces a single block. If nothing matches, the result is an empty list.
  • titlePath: Optional. JSONPath, relative to each block, selecting the value used as the block's title.
  • attributes: The values to display in each block. Each entry pairs a path (JSONPath, relative to each block) with a display label, which becomes the key in the key-value pairs returned to the wallet. If a path doesn't resolve for a block, that attribute is left out of the block.

These paths are evaluated against the transaction data as it is sent over the wire. For QES_APPROVAL, this means one titled block per document:

transactionDataProvider:
QES_APPROVAL:
type: QES_APPROVAL
display:
en: "QES transaction data"
order: 1
enabled: true
params:
public:
transactionDataDisplayParams:
groupPath: "$.documentInfos[*]"
titlePath: "$.label"
attributes:
- path: "$.access.oneTimePassword"
display: "transactionData.qesApproval.documentInfo.access.oneTimePassword"
- path: "$.href"
display: "transactionData.qesApproval.documentInfo.href"
- path: "$.checksum"
display: "transactionData.qesApproval.documentInfo.checksum"
- path: "$.signed_props"
display: "transactionData.qesApproval.documentInfo.signedProps"

The SCA types use snake_case field names nested under a payload wrapper, so they use groupPath: "$.payload" to produce a single untitled block per entry:

transactionDataProvider:
SCA_PAYMENT_CONFIRMATION:
type: SCA_PAYMENT_CONFIRMATION
display:
en: "Payment confirmation"
order: 2
enabled: true
params:
public:
leewaySeconds: 60
transactionDataDisplayParams:
groupPath: "$.payload"
attributes:
- path: "$.payee.name"
display: "transactionData.scaPaymentConfirmation.payee.name"
- path: "$.execution_date"
display: "transactionData.scaPaymentConfirmation.executionDate"

Leeway for SCA types​

All SCA provider types also require leewaySeconds, which sets how far in the past an executionDate can be and still be accepted as not yet past. QES_APPROVAL does not use it.

Presenting ISO mdocs​

Wallets can present ISO mdocs over OpenID4VP. ISO/IEC 18013-7 defines how mdocs are presented online, and its next edition is expected to build on HAIP, which profiles OpenID4VP 1.0 for both SD-JWT VC and mdoc credentials. Procivis One supports HAIP, so you can prepare a wallet to present mdocs this way now.

To do so, configure a dedicated OPENID4VP_FINAL1 instance for mdoc requests:

verificationProtocol:
MDOC_OPENID4VP:
display: "exchange.mdocOpenid"
order: 2
type: "OPENID4VP_FINAL1"
params:
public:
urlScheme: "<url scheme>"
holder:
supportedClientIdSchemes: [x509_hash]
  • urlScheme: Set this to the scheme the verifiers you work with use for mdoc requests. HAIP defines haip-vp; check the applicable edition of ISO/IEC 18013-7 or your ecosystem's requirements for the scheme to use. If verifiers use more than one scheme, configure one instance per scheme.
  • holder.supportedClientIdSchemes: HAIP requires wallets to accept x509_hash for signed requests.

As with any HAIP instance, the system does not check this configuration against HAIP or ISO/IEC 18013-7, so make sure the instance meets the requirements of the standard your verifiers follow.

Query language​

Verifiers using OpenID4VP 1.0 describe the credentials they want with the Digital Credentials Query Language (DCQL). DCQL support requires no configuration. For the DCQL features the wallet supports, see Submitting a Presentation.