Skip to main content

OpenID for Verifiable Presentations (OpenID4VP)

OpenID for Verifiable Presentations (OpenID4VP) lets a relying party request cryptographic proof of identity from a credential held in a user's digital wallet. Instead of a username and password, the wallet presents a Selective Disclosure JWT VC (SD-JWT VC). The credential is signed by a trusted authority: a government identity provider or a ThunderID credential issuer.

ThunderID acts as the verifier: it generates a signed authorization request, the wallet responds with an encrypted presentation, and ThunderID verifies the SD-JWT VC against a configured trust store. On success, it issues a signed result token to the relying party.

Presentation definitions are managed resources you create through the ThunderID Console. The verifier engine picks them up immediately when a relying party initiates a verification transaction.

How It Works

The protocol uses a polling model: a back-channel between ThunderID and the relying party, and an out-of-band channel to the wallet via QR code or deep link.

  1. An operator creates a presentation definition in the ThunderID Console, specifying which credential type (vct) to request and which claims are mandatory or optional.
  2. The relying party calls POST /openid4vp/initiate with the definition handle and receives a txn_id, wallet_url, and status_url.
  3. The relying party displays wallet_url as a QR code or deep link.
  4. The user scans. The wallet fetches the signed authorization request from GET /openid4vp/request.
  5. The wallet selects matching credentials, applies selective disclosure, and posts the encrypted response to POST /openid4vp/response.
  6. ThunderID decrypts the response, verifies the SD-JWT VC, and enforces the presentation policy.
  7. The relying party polls GET /openid4vp/status/{txn_id} until COMPLETED with a signed result_token.
  8. The relying party validates the result_token to read the verified subject and claims.
How ThunderID Implements It
AspectBehavior
Client identificationThe verifier's client_id is derived from client_id_scheme and the signing certificate at startup, not configured directly. Supported schemes: x509_hash (SHA-256 thumbprint), x509_san_dns (first DNS SAN), redirect_uri (response URI). The full certificate chain is included in the request object's x5c header.
Request object (JAR)Signed as a compact JWS and served at GET /openid4vp/request?state=... with Content-Type: application/oauth-authz-req+jwt.
Response modedirect_post.jwt. The wallet encrypts its VP token as a compact JWE using ECDH-ES key agreement with A128GCM content encryption (configurable via response_enc_values). The ephemeral public key is advertised in client_metadata.jwks.
Query languageDCQL (Digital Credentials Query Language). The query targets a specific vct and claims list.
Credential formatdc+sd-jwt.
Selective disclosureDisclosures beyond the combined requested claims list fail verification. Optional claims may be omitted by the wallet.
Key bindingWhen enforce_key_binding is enabled, the wallet must include a kb+jwt binding the presentation to the nonce and verifier audience.
Issuer trustEach definition may override the engine-level default via enforceTrustedIssuer. When enabled, the SD-JWT issuer is verified against a pinned certificate. A definition's trustedAuthorities restricts which named anchors are acceptable. Active trust anchors are listed at GET /openid4vp/trust-anchors (returns name, subject, ski, not_after per anchor).
Claim value constraintsclaimValues maps a dotted claim path to an allowed set of values. The constraint is advertised in the DCQL query and enforced at verification, but only when the claim is disclosed; undisclosed claims pass silently.
Result tokenSigned JWT issued on COMPLETED. Claims: iss, sub, aud (issuer URL), jti, txn, definition_id, subject, verified_claims, verifier.
State TTLTransactions expire after state_ttl_seconds. The status endpoint returns EXPIRED for past-TTL transactions.
Wallet errorsA wallet may POST an OAuth error instead of a VP token (OpenID4VP §6.4). ThunderID records the error and reflects it as FAILED in the status response.

Presentation Definitions

A presentation definition specifies the credential type to request and which claims the wallet must or may disclose. Its handle is the definition_id used when initiating a verification.

Manage definitions in Verifiable Credentials → Presentations in the ThunderID Console, or declare them in YAML for file-based deployments (resource_type: presentation_definition).

FieldDescription
HandleUnique identifier for this definition. Used as the definition_id in the initiate request and in flow step configuration. Required.
Credential Type (VCT)The vct URI the wallet must present to satisfy this request. Required.
ClaimsEach entry has a dotted claim path, a requirement (Mandatory or Optional), and an optional allowed-values list. Mandatory claims must be disclosed; optional claims may be withheld. When allowed values are set, the disclosed value must match one, enforced at verification. Undisclosed optional claims pass silently regardless of allowed values.
Enforce Trusted IssuerWhen enabled, the SD-JWT VC issuer certificate chain is validated against the engine-level trust anchors.
Trusted IssuersRestricts which named trust anchors are acceptable for this definition. Leave empty to accept any trust anchor configured at the engine level.

Server Configurations

Configure the verifier engine under the openid4vp: key in deployment.yaml. All values can be overridden there; the defaults below come from the built-in server defaults.

KeyDefaultDescription
client_id_schemex509_san_dnsHow the verifier client_id is derived from the signing certificate: x509_san_dns (first DNS SAN), x509_hash (SHA-256 thumbprint), or redirect_uri (response URI).
signing_key_idecdsa-keyKey used to sign request objects. Must be certificate-backed.
ephemeral_key_idvp-encKey used for ECDH-ES decryption of wallet responses.
registration_cert_file-Path to a PEM file containing the signing certificate chain advertised in client_metadata.
trusted_anchors[]List of {name, cert_file} entries. Each entry pins a root CA whose chains are accepted as SD-JWT VC issuer certificates. Empty list disables certificate-chain validation.
enforce_key_bindingtrueRequire wallets to include a kb+jwt binding the presentation to the nonce and verifier.
response_enc_values["A128GCM","A256GCM","A128CBC-HS256","A256CBC-HS512"]Supported content-encryption algorithms for the wallet's JWE response.
request_audience-aud claim placed in the signed request object sent to the wallet. Omit for vp_token flows (some wallets treat a non-empty aud as a SIOP request).
request_validity_seconds300How long the signed request object is valid.
state_ttl_seconds300Verification transaction lifetime (seconds).
leeway_seconds30Clock-skew tolerance when validating credential timestamps.
key_binding_max_age_seconds300Maximum age of the key-binding JWT iat.
result_token_validity_seconds300Lifetime of the signed result token.

trusted_anchors is a list of objects:

openid4vp:
trusted_anchors:
- name: my-gov-ca
cert_file: /path/to/ca.pem

Result Token Claims

When the status endpoint returns COMPLETED, the response includes a signed JWT result_token. Validate it against the ThunderID JWKS before trusting its claims.

ClaimDescription
issThe ThunderID instance base URL.
subThe holder's stable identifier, from the credential sub claim, or a key-binding thumbprint URN when sub is absent.
audThe ThunderID issuer URL.
jtiUnique token identifier (same value as txn).
txnThe transaction identifier (txn_id).
definition_idThe presentation definition handle that was requested.
subjectSame as sub.
verified_claimsMap of selectively disclosed claim values from the credential.
verifierThe verifier's client_id.

Try It in ThunderID

Initiate a Presentation Transaction

POST /openid4vp/initiate
Content-Type: application/json

{ "definition_id": "example-pid" }

Response:

{
"txn_id": "a1b2c3d4e5f6",
"wallet_url": "openid4vp://?client_id=x509_hash%3A...&request_uri=https%3A%2F%2Fauth.example.com%2Fopenid4vp%2Frequest%3Fstate%3Da1b2c3d4e5f6&request_uri_method=get",
"status_url": "/openid4vp/status/a1b2c3d4e5f6",
"expires_at": "2026-01-01T12:05:00Z"
}

Display wallet_url as a QR code or deep link. Then poll until the wallet responds:

GET /openid4vp/status/a1b2c3d4e5f6
{
"status": "COMPLETED",
"result_token": "<signed-jwt>"
}

Verify the result_token signature against the ThunderID JWKS endpoint, then read verified_claims.

Use in a Sign-In Flow

To integrate OpenID4VP into an authentication flow, configure the OpenID4VP Verify executor. It manages the initiate-and-poll lifecycle automatically, presenting the wallet QR to the user and resolving their identity on completion.

See Advanced Configurations: OpenID4VP Verify for the executor reference.

Explore with AI

ThunderID LogoThunderID Logo

Product

DocsAPIsSDKs
© Copyright Linux Foundation Europe.For web site terms of use, trademark policy and other project policies please see https://linuxfoundation.eu/en/policies.