OID4VP
OID4VP
Section titled “OID4VP”Standards
Section titled “Standards”- OpenID for Verifiable Presentations 1.0 (final)
- OpenID4VC High Assurance Interoperability Profile (HAIP) 1.0 (final)
- W3C Digital Credentials API request/response processing (backend API and tests; no browser executor)
- OAuth 2.0 Authorization Framework (request/response parameter model)
- JOSE (JWT/JWS/JWE processing for request objects,
direct_post.jwt, anddc_api.jwt) - DID Core / did:web (for
decentralized_identifierclient_id scheme handling) - RFC 5280 / PKIX (X.509 certificate chain validation for
x509_san_dnsandx509_hash)
Available Flows
Section titled “Available Flows”| Flow ID | Name | Steps | Description |
|---|---|---|---|
oid4vp-direct-post |
DCQL + direct_post | 4 | Verifier creates signed request object, wallet fetches from request_uri, posts vp_token + state to response_uri, verifier validates and evaluates policy |
oid4vp-direct-post-jwt |
DCQL + direct_post.jwt | 4 | Verifier creates signed request, wallet fetches from request_uri, posts encrypted JWE containing signed oauth-authz-resp+jwt, verifier decrypts and evaluates policy |
The table lists the flows available in Looking Glass. profile: "haip" and
dc_api.jwt are backend API capabilities covered by Go tests; there is no
Looking Glass DC API flow, and the wallet harness currently accepts only
direct_post and direct_post.jwt.
Demo Scenarios
Section titled “Demo Scenarios”- DCQL direct_post – Create request object, hand off to wallet, evaluate verifier policy
- DCQL direct_post.jwt – Same flow with encrypted response transport and inner JWT validation
Wallet Credential Profile Matching
Section titled “Wallet Credential Profile Matching”The Looking Glass OID4VP controls preflight the selected VC credential profile against the DCQL preset before wallet submission. The default preset and credential profile both target the mDL mso_mdoc. If the preset requests mso_mdoc, dc+sd-jwt, jwt_vc_json, jwt_vc_json-ld, or ldp_vc, the wallet auto-issue profile must issue the matching format. Otherwise the wallet action is blocked with an explanation, because the wallet cannot build a matching vp_token from the selected profile.
When a credential_jwt is pasted manually, the UI allows submission but warns when the selected auto-issue profile does not match the DCQL format. The wallet and verifier still validate the actual credential and will reject the presentation if the credential evidence does not satisfy the request.
For JOSE/W3C credentials, verifier policy also requires issuance lineage in
ProtocolSoup’s credential store; this demo verifier is not a general trust-list
verifier for arbitrary externally issued credentials. The mso_mdoc path uses
independently configured IACA trust anchors instead.
Example DCQL Query
Section titled “Example DCQL Query”The default and canonical presentation request targets the ISO/IEC 18013-5 mobile driving licence (mso_mdoc). An authorization request created without an explicit dcql_query or scope receives this query, requesting family_name and document_number from the org.iso.18013.5.1 namespace:
{ "credentials": [{ "id": "mdl", "format": "mso_mdoc", "meta": { "doctype_values": ["org.iso.18013.5.1.mDL"] }, "claims": [ { "path": ["org.iso.18013.5.1", "family_name"] }, { "path": ["org.iso.18013.5.1", "document_number"] } ] }]}SD-JWT VC and the W3C formats remain fully selectable by sending an explicit dcql_query (or scope) that names them. For example, a university_degree SD-JWT VC requesting degree and graduation_year:
{ "credentials": [{ "id": "university_degree", "format": "dc+sd-jwt", "claims": [ { "path": ["degree"] }, { "path": ["graduation_year"] } ], "meta": { "vct_values": ["https://protocolsoup.com/credentials/university_degree"] } }]}Request and Response Contracts
Section titled “Request and Response Contracts”- Authorization request enforces XOR: exactly one of
dcql_queryorscope. - For
response_modedirect_postanddirect_post.jwt,response_uriis required andredirect_urimust be absent. - For
response_modedc_apianddc_api.jwt(the W3C Digital Credentials API),response_uriandstateare not used; the request carriesorigin(andexpected_originsfor signed requests), and the wallet response is correlated byrequest_id. - Request object header
typmust beoauth-authz-req+jwt. - VP token header
typmust bevp+jwt(JWT/SD-JWT VP tokens; not used bymso_mdoc). direct_post.jwtinner response headertypmust beoauth-authz-resp+jwt(SD-JWT path; themso_mdoconline profile uses an unsigned encrypted JWT, see below).
ISO mdoc online profile (mso_mdoc)
Section titled “ISO mdoc online profile (mso_mdoc)”ProtocolSoup verifies ISO/IEC 18013-5 mso_mdoc presentations over the OpenID4VP
1.0 online profile (Appendix B.2), alongside the SD-JWT VC path, which is
unchanged.
- vp_token shape. With DCQL, the
vp_tokenis a JSON object keyed by the DCQL credential queryid, whose value is an array of base64url-encoded CBORDeviceResponsestrings:{ "<dcql_id>": ["<base64url DeviceResponse>"] }. - SessionTranscript / handover. The device signature is bound to the request
through
SessionTranscript = [null, null, OpenID4VPHandover](OID4VP 1.0 Appendix B.2.6.1). The verifier reconstructs theOpenID4VPHandoveritself from the request’sclient_id,nonce,response_uri, and the RFC 7638 JWK SHA-256 thumbprint of its response-encryption key; it never trusts a transmitted handover. The handover identifier is the fixed string"OpenID4VPHandover", and the second element isSHA-256over the CBOR of[client_id, nonce, jwkThumbprint, response_uri]. - Encrypted responses (
direct_post.jwt). For anmso_mdocrequest withresponse_mode=direct_post.jwt, the verifier provisions an ephemeral EC (P-256) ECDH-ES response-encryption key and advertises its public half inclient_metadata.jwkswithencrypted_response_enc_values_supported(A128GCM). The wallet encrypts the Authorization Response as a JWE (ECDH-ES + A128GCM) whose payload carriesvp_tokenandstateas top-level members. ThejwkThumbprintbound into the handover is the thumbprint of the key actually used to encrypt, so a response re-encrypted to a different key is detected as a device-authentication failure (anti-substitution, Appendix B.2.6). Response encryption is mandatory for the online profile. - Issuer trust. The presented document-signer certificate must chain to a
configured ISO/IEC 18013-5 IACA trust anchor (
MDOC_IACA_ROOT_PEM/MDOC_IACA_ROOT_PEM_FILE); the digests,validityInfo, and the detacheddeviceSignatureover the reconstructedDeviceAuthenticationBytesare all verified.
Example DCQL query requesting an mdoc mobile driving licence:
{ "credentials": [{ "id": "mdl", "format": "mso_mdoc", "meta": { "doctype_values": ["org.iso.18013.5.1.mDL"] }, "claims": [ { "path": ["org.iso.18013.5.1", "family_name"] }, { "path": ["org.iso.18013.5.1", "document_number"] } ] }]}HAIP mode and the W3C Digital Credentials API
Section titled “HAIP mode and the W3C Digital Credentials API”ProtocolSoup implements the OpenID4VC High Assurance Interoperability Profile
(HAIP 1.0, final) as an opt-in mode and adds the browser-mediated W3C Digital
Credentials API presentation path. Both layer on top of the general SD-JWT VC
and mso_mdoc support, which stays available and unchanged outside HAIP / DC API
mode.
HAIP mode (profile: "haip")
Section titled “HAIP mode (profile: "haip")”HAIP is a constraining profile, not an additive option. When a request sets
profile: "haip", the verifier enforces the pinned constraints and rejects
out-of-profile choices at request creation:
- DCQL is required (a
scopealias is rejected). - The response mode must be encrypted (
direct_post.jwtordc_api.jwt); plaindirect_post/dc_apiis rejected. - The signed-request Client Identifier Prefix must be
x509_hash(the HAIP-mandated scheme).verifier_attestation,x509_san_dns,redirect_uri, andpre_registeredare rejected in HAIP mode. - ES256 / SHA-256 baseline; HAIP sessions advertise both
A128GCMandA256GCMinencrypted_response_enc_values_supported, and ECDH-ES response encryption is provisioned for bothmso_mdocanddc+sd-jwt. - Each
mso_mdoccredential in thedcql_querygains an AKI Trusted Authorities Query (OID4VP 1.0 Section 6.1.1,aki), with the value set to the base64urlSubjectKeyIdentifierof the configured IACA root. The wallet uses it to select an issuer the verifier trusts; the verifier validates both that the document-signer chains to a configured IACA root and that its certificate Authority Key Identifier is one of the requested values. An explicittrusted_authoritieson a credential is preserved, and non-mdoc credentials are left untouched.
W3C Digital Credentials API path (dc_api, dc_api.jwt)
Section titled “W3C Digital Credentials API path (dc_api, dc_api.jwt)”The backend implements the request-object, handover, audience, decryption, and policy-processing side of this path. The repository does not currently invoke the browser’s Digital Credentials API, so this is not exposed as an end-to-end browser demo.
In a conforming browser invocation, the request is delivered browser-mediated rather than by redirect / direct_post.
The verifier sets response_mode: "dc_api.jwt" and the Verifier Web origin
(optionally expected_origins for signed requests). There is no response_uri
and no state; the wallet response is correlated by request_id. The response
audience is the Origin prefixed with origin: (the KB-JWT aud for SD-JWT
VC; the bare Origin inside the mdoc handover). This path serves both SD-JWT VC
and mso_mdoc.
The DC API path uses a distinct SessionTranscript handover,
OpenID4VPDCAPIHandover (OID4VP 1.0 Appendix B.2.6.2), built over
[origin, nonce, jwkThumbprint] — not the redirect OpenID4VPHandover
over [client_id, nonce, jwkThumbprint, response_uri]. The fixed identifier
string is "OpenID4VPDCAPIHandover" and the info array has 3 elements (the
Origin replaces client_id and response_uri), so the two handovers can never
collide. The verifier selects the variant by invocation path and reconstructs it
from its own inputs; it never trusts a transmitted handover.
Attestation
Section titled “Attestation”HAIP 1.0 makes wallet/key attestation support mandatory but leaves the wire
format an Ecosystem extension point (HAIP §9.3); no single attestation format is
pinned by HAIP itself, so the presentation profile requires none. The
verifier_attestation signed-request scheme remains available in the general
profile, but is rejected when profile: "haip" is selected.
Client ID Schemes
Section titled “Client ID Schemes”OpenID4VP defines multiple client identification schemes. ProtocolSoup implements the following:
redirect_uri
Section titled “redirect_uri”Verifier identity is established by the redirect URI trust context. The client_id equals the response_uri.
decentralized_identifier (did:web)
Section titled “decentralized_identifier (did:web)”Verifier identity is resolved via DID document. The DID document is fetched and validated at runtime – id must match the presented DID, and verification material must be present in authentication, assertionMethod, or verificationMethod.
verifier_attestation
Section titled “verifier_attestation”Verifier presents a signed attestation JWT. The verifier’s attestation issuer publishes OpenID discovery metadata and JWKS. The wallet validates the attestation JWT against the published JWKS. When OID4VP_VERIFIER_ATTESTATION_PRIVATE_KEY_PEM is unset, an ephemeral in-memory key is auto-provisioned at startup.
x509_san_dns (OpenID4VP Section 5.9)
Section titled “x509_san_dns (OpenID4VP Section 5.9)”Verifier identity is bound to a DNS name via X.509 certificate Subject Alternative Name. The request object carries an x5c JOSE header containing the certificate chain. Trust validation requires:
- PKIX chain validation (leaf signed by CA, validity period, key usage)
- Leaf certificate DNS SAN matches the
client_idDNS name response_urihostname matches theclient_idDNS name- JWT signature verifies against the leaf certificate public key
- The
client_id_schemeclaim is present in the signed request object
When no certificate material is configured via environment variables, the verifier auto-provisions an ephemeral ECDSA P-256 self-signed CA + leaf chain at startup, with the leaf SAN bound to the deployment hostname.
x509_hash (HAIP 1.0 §5; OpenID4VP §5.9.3)
Section titled “x509_hash (HAIP 1.0 §5; OpenID4VP §5.9.3)”The HAIP-mandated Client Identifier Prefix for signed requests. The client_id
is x509_hash:<base64url(SHA-256(leaf-cert-DER))> — verifier identity is bound to
the hash of the leaf certificate’s DER encoding rather than a DNS SAN. The request
object carries the x5c chain. This is the default scheme for HAIP / DC API
requests and reuses the same X.509 signer as x509_san_dns. When a persistence
root (data dir) is configured the auto-provisioned signing certificate is
persisted and reloaded across restarts, so the x509_hash client_id stays
stable for a certification window rather than churning every process start; the
wallet trusts the request signature by that leaf hash.
The wallet harness performs PKIX validation against its independently configured
trust store (WALLET_VERIFIER_X509_TRUST_ANCHOR_PEM); an x5c chain cannot
appoint its own trust root.
Not implemented
Section titled “Not implemented”pre_registered,openid_federationare defined in the spec but not currently implemented.
Verifier Evaluation Flow (OID4VP §5, §6, §8)
Section titled “Verifier Evaluation Flow (OID4VP §5, §6, §8)”- Create Authorization Request — Verifier builds a signed request object (
typ=oauth-authz-req+jwt) containingclient_id,response_type=vp_token,response_mode,response_uri,dcql_query(orscope),nonce,state, andexp. Publishes it at arequest_uri. - Wallet Fetches Request Object — Wallet fetches the signed request from
request_urivia GET or POST. Validates JWT signature,typheader, and expiry before processing. - Wallet Submits Response — Wallet evaluates DCQL query, selects matching credentials, creates the
vp_token, and submits it:direct_post: form-postvp_token+statedirect_post.jwt: form-post encryptedresponseJWE containing signed inner JWT (typ=oauth-authz-resp+jwt) withvp_token,state, and audience bound toresponse_uridc_api.jwt(W3C Digital Credentials API): post encryptedresponseJWE +request_id(nostate), with the response audience bound toorigin:<verifier-origin>
- Verifier Validates + Policy Decision — Verifier resolves the session by
state(redirect path) orrequest_id(DC API path), then validates:- nonce binding (VP token nonce matches request nonce)
- audience binding (VP token audience includes
client_id, ororigin:<origin>on the DC API path) - token expiry
- holder binding (
iss/submatches wallet identity,cnf.jktmatches key thumbprint)
- credential evidence (issuer signature, subject binding, disclosure integrity, and trust policy checks)
- DCQL claim-path completeness (all required claim paths present in disclosed claims)
- Result — Policy decision is stored and returned as
allowed/deniedwithcode,message,reasons, andreason_codes.
Endpoints
Section titled “Endpoints”| Endpoint Role | Purpose |
|---|---|
| Authorization Request Endpoint | Verifier creates and publishes a request object / request URI |
| Request URI Retrieval Endpoint | Wallet fetches request object by request_uri |
| Verifier Attestation Discovery | OpenID discovery metadata for the verifier attestation issuer |
| Verifier Attestation AS Metadata | OAuth 2.0 Authorization Server metadata for the attestation issuer |
| Verifier Attestation JWKS | Public signing keys used to validate verifier_attestation request objects |
| Response Endpoint | Wallet submits direct_post / direct_post.jwt (correlated by state) or dc_api.jwt (correlated by request_id) response payloads |
| Verification Result Endpoint (optional) | Verifier exposes policy evaluation outcome to client tooling |
Illustrative Examples (Non-Normative)
Section titled “Illustrative Examples (Non-Normative)”direct_postresponse parameters:state=<opaque-value>&vp_token=<compact-jws>direct_post.jwtresponse parameter:response=<compact-jwe>where the decrypted plaintext is an inner JWT withtyp=oauth-authz-resp+jwt- VP token essentials:
typ=vp+jwt, nonce bound to request nonce, audience bound to verifierclient_id
What To Validate
Section titled “What To Validate”- Authorization request: signed request object with
typ=oauth-authz-req+jwt,client_id,response_mode,response_uri,nonce,state,exp - DCQL contract: exactly one of
dcql_queryorscope; required claim paths derived from DCQL are enforced - Client identification: chosen client_id scheme follows OpenID4VP scheme-specific trust validation
direct_postresponse:statemaps to active request andvp_tokenis presentdirect_post.jwtresponse: decrypts successfully and inner JWT signature/type/audience/subject/expiry checks pass- VP token checks:
typ=vp+jwt, nonce match, audience match, expiry valid, holder binding verified - Credential evidence checks: presented credential subject/issuer/signature/disclosures satisfy verifier trust policy and required claim paths
- Policy output: verifier returns explicit decision with status, code, and reason details