OID4VP
OID4VP
Section titled “OID4VP”ProtocolSoup’s OID4VP Verifier and Wallet implementations are OpenID Certified™ through the OpenID Foundation certification programme (HAIP, SD-JWT VC, and mdoc). Certification applies to the named versions and profiles, not every ProtocolSoup feature.
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 an ECDH-ES response JWE whose payload is the JSON Authorization Response (vp_token + state), 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. The hosted wallet harness accepts direct_post and
direct_post.jwt, and exposes a public /authorize authorization endpoint for
HAIP direct_post.jwt wallet handoff.
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 ECDH-ES response transport (OID4VP 1.0 Section 8.3)
Wallet Credential Format Matching
Section titled “Wallet Credential Format Matching”The Looking Glass OID4VP controls preflight the selected credential format against the DCQL query before wallet submission. Format (mso_mdoc or dc+sd-jwt) and HAIP (general or HAIP) are independent selectors. The default is the mDL mso_mdoc. HAIP on OID4VP issues the key-attested configuration of that format (MobileDrivingLicenceMsoMdocHAIP or UniversityDegreeCredentialSDJWTHAIP) and applies HAIP presentation: x509_hash plus encrypted direct_post.jwt (HAIP 1.0 Section 5). Wallet and key attestation (HAIP 1.0 Sections 4.4.1 and 4.5.1) are the issuance half of that selector, not a second dropdown. Custom DCQL, including W3C jwt_vc_json / jwt_vc_json-ld / ldp_vc, can still be pasted under Advanced; the wallet action is blocked unless the selected or pasted credential matches the requested format. On the unencrypted direct_post flow, Looking Glass shows that HAIP coerces the response to direct_post.jwt before Execute. Wallet x5c trust failures against WALLET_VERIFIER_X509_TRUST_ANCHOR_PEM keep the protocol error; the hosted wallet already trusts the showcase verifier CA.
When a credential_jwt is pasted manually, the UI allows submission but warns when the selected auto-issue format 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. Independently issued
certificate-backed SD-JWT VCs can still be accepted when they chain to
OID4VP_SD_JWT_TRUST_ANCHOR_PEM. The mso_mdoc path uses independently
configured IACA trust anchors. HAIP dc+sd-jwt credentials include an OAuth
Token Status List; the verifier fetches it and validates the token with the
same issuer key already accepted for the credential.
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_value": "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 selectable by sending an explicit
dcql_query that names them. Scope aliases are not currently resolved to
DCQL and are therefore not conformance evidence. For example:
{ "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. - The repository does not currently provide a browser-mediated W3C Digital
Credentials API flow. Private
request_idAPIs are not classified as DC API conformance evidence. - Request object header
typmust beoauth-authz-req+jwt. - VP token header
typmust bevp+jwt(JWT/SD-JWT VP tokens; not used bymso_mdoc). - Encrypted
direct_post.jwtresponses are JWEs (ECDH-ES) whose payload is the JSON Authorization Response (vp_tokenandstateas top-level members). The HTTP form contains onlyresponse(OID4VP 1.0 Section 8.3.1).
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). Forresponse_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; HAIP also listsA256GCM). The wallet encrypts the Authorization Response as a JWE (ECDH-ES + AES-GCM) whose payload carriesvp_tokenandstateas top-level members. Formso_mdoc, 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). - 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. Annex B DS profile checks apply to IACA→DS chains; when the configured trust anchor itself signs IssuerAuth (self-signed trust-anchor-as-document-signer), those DS-only checks are skipped after path validation.
Example DCQL query requesting an mdoc mobile driving licence:
{ "credentials": [{ "id": "mdl", "format": "mso_mdoc", "meta": { "doctype_value": "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 verifier-side HAIP constraints as an opt-in mode.
The hosted web wallet implements the OID4VP wallet HAIP profile for
response_mode=direct_post.jwt with client_id_prefix=x509_hash and
request_method=request_uri_signed for both sd_jwt_vc and iso_mdl.
Browser-mediated W3C Digital Credentials API wallet invocation (dc_api.jwt)
remains deferred: it requires a native wallet provider (for example Android
Credential Manager), not a hosted web wallet.
Wallet HAIP (direct_post.jwt)
Section titled “Wallet HAIP (direct_post.jwt)”- Public authorization endpoint:
https://wallet.protocolsoup.com/authorize(client_id,request_uri,request_uri_method). - Request-object retrieval supports GET and RFC-compatible POST (with
wallet_nonce). - External
x509_hashverifiers are accepted only after the request object’sx5cchain validates toWALLET_VERIFIER_X509_TRUST_ANCHOR_PEM(or system roots), the leaf hash matches thex509_hash:client_id, and the holder approves the external verifier in the consent UI. - Presentations return DCQL-keyed
vp_tokenobjects. SD-JWT disclosures and mdoc IssuerSigned elements are derived from DCQL claim paths (OID4VP §6.4.1: absent/emptyclaimsreturns only mandatory material — no SD-JWT disclosures and no selectively disclosable mdoc elements). Only Credential Query ids the presented credential actually satisfies are included asvp_tokenkeys (optionalcredential_setsentries that do not match are omitted). Encrypted responses use ECDH-ES JWE (response=<compact JWE>). - After a successful HAIP response, the wallet navigates a verifier-provided
redirect_uriwhen present and safe (httpsonly, no userinfo, no loopback, private, or internal host). The SPA applies the same check beforewindow.location.assign. dc_api.jwtremains deferred pending a native wallet provider.
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 each 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 Authority Key Identifier is one of the requested values. When IssuerAuth is signed by a self-signed trust anchor that omits AuthorityKeyIdentifier, the leaf SubjectKeyIdentifier is used for that match. 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 contains request-object, handover, audience, decryption, and policy
building blocks for this path. The repository does not invoke the browser’s
Digital Credentials API, and the hosted wallet explicitly defers dc_api.jwt
pending a native provider. These private APIs are implementation scaffolding,
not a conforming DC API wallet flow.
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 Identifier Prefix is inferred from
client_id; the removed draftclient_id_schemeparameter is not emitted.
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 without the root trust anchor. This is the
default scheme for HAIP
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 across process restarts instead of changing every boot; wallets that
pin trust by leaf hash can keep a stable verifier identity.
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”openid_federationis not currently implemented. Unknown Client Identifier Prefixes follow the Final fallback policy and are treated as pre-registered.
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,aud=https://self-issued.me/v2) 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 compact signed JWT
from
request_uriwith media typeapplication/oauth-authz-req+jwt. GET is the default;request_uri_method=post(OpenID4VP 1.0 Section 5.10) has the wallet POST awallet_nonceand receive a request object bound to that nonce. The wallet validates signature,typ, and expiry. - 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 (ECDH-ES) whose JSON payload containsvp_tokenandstate
- Verifier Validates + Policy Decision — Verifier requires exact
statecorrelation for redirect response modes. Whendirect_post.jwtomits an outerstate, the JWEkidselects the request’s ephemeral decryption key and the decryptedstatemust then match that request exactly. It 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, and for SD-JWT VC, the Key Binding JWTiatis within a 5-minute freshness window)
- credential evidence (issuer signature, subject binding, disclosure integrity, and trust policy checks)
- DCQL claim-path completeness: all required claim paths present in disclosed claims, or, when a Credential Query declares
claim_sets, at least one alternative combination of claimids fully disclosed - DCQL
credential_sets(when present): each required Credential Set Query’soptionshas at least one fully-matched combination of Credential Queryids;required: falseentries do not block the decision
- 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 exact state |
| 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 JSON{ "vp_token": ..., "state": ... }- 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, includingclaim_setsalternatives and top-levelcredential_setscombinations when present - Client identification: chosen client_id scheme follows OpenID4VP scheme-specific trust validation
direct_postresponse:statemaps to active request andvp_tokenis presentdirect_post.jwtresponse: JWE decrypts with the request’s ephemeral ECDH-ES key and the payloadstatematches the authorization request- 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