OID4VCI
OID4VCI
Section titled “OID4VCI”Standards
Section titled “Standards”- OpenID for Verifiable Credential Issuance 1.0 (final)
- OAuth 2.0 Authorization Framework (token and grant semantics)
- RFC 8414 OAuth 2.0 Authorization Server Metadata
- OAuth 2.0 Attestation-Based Client Authentication (draft-ietf-oauth-attestation-based-client-auth)
- OID4VCI 1.0 Appendix D Key Attestation
- High Assurance Interoperability Profile (HAIP) 1.0 building blocks (full profile conformance is not claimed)
- SD-JWT VC profile (
dc+sd-jwtcredential format) - W3C Verifiable Credentials Data Model 1.1 / 2.0 (
jwt_vc_json,jwt_vc_json-ld,ldp_vcformats) - W3C VC Data Integrity 1.0 (
ecdsa-rdfc-2019,eddsa-rdfc-2022cryptosuites forldp_vc) - ISO/IEC 18013-5 mobile driving licence (
mso_mdoccredential format, doctypeorg.iso.18013.5.1.mDL)
Available Flows
Section titled “Available Flows”| Flow ID | Name | Steps | Description |
|---|---|---|---|
oid4vci-pre-authorized |
Pre-Authorized Code | 6 | Resolve offer, discover metadata, exchange pre-authorized code, receive token + c_nonce, submit proof, receive issued credential |
oid4vci-pre-authorized-tx-code |
Pre-Authorized + tx_code | 6 | Same flow with mandatory tx_code at token exchange |
oid4vci-deferred-issuance |
Deferred Issuance | 8 | Full end-to-end flow: offer, metadata, token, credential request, deferred response with transaction_id, polling, credential ready |
Demo Scenarios
Section titled “Demo Scenarios”- Pre-Authorized Issuance – Resolve by-reference offer, exchange token, submit proof, receive issued credential. The default and lead credential configuration is the ISO/IEC 18013-5 mobile driving licence (
mso_mdoc); an offer or credential request that does not name a configuration receives the mDL. - Pre-Authorized + tx_code – Enforce out-of-band
tx_codebefore token issuance - Deferred Issuance Polling – Receive
transaction_id, poll until credential is ready
Credential Details
Section titled “Credential Details”| Property | Value |
|---|---|
| Credential configuration selection | Wallet chooses from credential_configurations_supported. The default and lead entry is the mDL (MobileDrivingLicenceMsoMdoc); omitting an explicit configuration selects it. |
| Credential formats | mso_mdoc (ISO/IEC 18013-5 mDL, default), dc+sd-jwt (SD-JWT VC), jwt_vc_json, jwt_vc_json-ld, ldp_vc (W3C Data Integrity) |
| Proof container | proof or proofs with proof_type: jwt (e.g., {"proof_type":"jwt","jwt":"<compact-jws>"}) |
mso_mdoc device key binding |
Proof cnf.jwk must be an EC P-256 key; it is bound into the MSO deviceKeyInfo.deviceKey as a COSE_Key |
| Proof JWT header type | openid4vci-proof+jwt |
| Proof JWT required claims | iss, sub, aud, nonce, iat, exp, cnf.jwk |
| Deferred issuance indicator | transaction_id returned by credential endpoint (e.g., an opaque identifier such as "txn_...") |
Pre-Authorized Code Issuance Flow (OID4VCI §4, §6.1, §7)
Section titled “Pre-Authorized Code Issuance Flow (OID4VCI §4, §6.1, §7)”- Resolve Credential Offer — Wallet receives
credential_offer_uriout-of-band and fetches the credential offer object from the Credential Issuer. Offer containscredential_issuer,credential_configuration_ids, andgrants.urn:ietf:params:oauth:grant-type:pre-authorized_code. - Discover Issuer Metadata — Wallet fetches
/.well-known/openid-credential-issuerto discovercredential_configurations_supported,credential_endpoint,nonce_endpoint,deferred_credential_endpoint, and token endpoint URL. - Token Request — Wallet calls token endpoint with
grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code,pre-authorized_code, andtx_codewhen required by the offer grant. - Token Response — Authorization Server validates the pre-authorized code (and
tx_codeif required) and returnsaccess_token,token_type,scope(if applicable),c_nonce, andc_nonce_expires_in. - Credential Request — Wallet submits
credential_configuration_idandproof/proofs(proof_type: jwt) with a proof JWT bound to the activec_nonceand the credential issuer audience. - Credential Response — Issuer validates proof key binding, nonce freshness, and audience/subject constraints, then returns:
- Immediate:
formatmatching the credential configuration (e.g.dc+sd-jwtwith disclosures,jwt_vc_json,jwt_vc_json-ld,ldp_vcwith Data Integrity proof, ormso_mdocas base64url-encoded CBORIssuerSigned),credential, nextc_nonce, or - Deferred:
transaction_id, nextc_nonce— wallet then pollsdeferred_credentialendpoint until credential is ready.
- Immediate:
Authorization Code Flow
Section titled “Authorization Code Flow”In addition to pre-authorized_code, the token endpoint accepts
grant_type=authorization_code. The Authorization Server (the mockidp-backed
/oidc/authorize and /oid4vci/token) is discoverable via RFC 8414 metadata:
- Wallet reads
authorization_serversfrom the Credential Issuer Metadata (defaults to the issuer’s own identifier). - Wallet fetches
{authorization_server}/.well-known/oauth-authorization-server(RFC 8414 Section 3.1 well-known URI insertion — for issuerhttps://host/oid4vcithis resolves tohttps://host/.well-known/oauth-authorization-server/oid4vci) to discoverauthorization_endpoint,token_endpoint,grant_types_supported,code_challenge_methods_supported(S256), andtoken_endpoint_auth_methods_supported. - Wallet drives the standard OAuth 2.0 authorization code flow (with PKCE)
against
authorization_endpoint, then exchanges the code attoken_endpointexactly as it would forpre-authorized_code, receivingaccess_tokenandc_nonce.
This path is implemented and covered at the HTTP/API layer. The current Looking Glass OID4VCI executors demonstrate only the pre-authorized-code variants; they do not yet drive this authorization-code flow.
HAIP issuance building blocks
Section titled “HAIP issuance building blocks”The issuer implements the following HAIP-related issuance controls as opt-in, trust-anchor-gated API features. This is not a claim of complete HAIP issuer support: DPoP sender-constrained access tokens are not implemented, and the Looking Glass and wallet harness do not yet generate the client-attestation, key-attestation, and encrypted-credential-request material for an end-to-end HAIP issuance demo. The unattested Final flow remains unaffected by default.
- Client attestation (OAuth 2.0 Attestation-Based Client Authentication).
The token endpoint accepts an
OAuth-Client-Attestation+OAuth-Client-Attestation-PoPheader pair as an alternative toclient_secret/Basic auth. The attestation JWT’sx5cchain is verified against a configured trust anchor, itscnf.jwkbecomes the Client Instance Key that must sign the PoP JWT, and the PoP’sjtiis single-use. When both headers are present, they are the authentication verdict for the request — a failed attestation is never silently downgraded to a weaker method. The attestedsubbecomes the authoritativeclient_id. Advertised in AS metadata’stoken_endpoint_auth_methods_supportedasattest_jwt_client_authonly once a trust anchor is configured. - Key attestation (OID4VCI 1.0 Appendix D). Credential configurations may
set
key_attestations_required(advertised underproof_types_supported.jwtin Credential Issuer Metadata). For those configurations, the proof JWT’s JOSE header must carry akey_attestationJWT (typ: key-attestation+jwt) whosex5cchain is verified against a configured trust anchor, and whoseattested_keysmust include the proof’scnf.jwkholder key and satisfy any requiredkey_storage/user_authenticationattack-potential levels. - Encrypted credential responses. A wallet may include a
credential_response_encryptionobject (jwk,enc) on the Credential or Deferred Credential Request. When present and valid, the response is returned as a compact JWE (Content-Type: application/jwt) — ECDH-ES key agreement to the wallet’s ephemeral key, then AES-GCM content encryption (A128GCMorA256GCM) — instead of plain JSON. Advertised in Credential Issuer Metadata’scredential_response_encryptionobject;encryption_requiredstaysfalse.
These controls are exercised by backend regression tests. OIDF suite validation is still required before any certification or external-conformance claim.
Contract Enforcement
Section titled “Contract Enforcement”- Credential offer envelope enforces XOR: exactly one of
credential_offerorcredential_offer_uri. tx_codeis required when the offer grant includes atx_codeobject.- Proofs are required when proof types are declared for the credential configuration.
- Proof JWT header
typmust beopenid4vci-proof+jwt. - Proof must include
iss,sub,aud,nonce,iat,exp, andcnf.jwk. - Nonce is freshness-bound and one-time in issuance requests (replay and expiry are rejected).
- Deferred polling requires matching
transaction_idand same access token lineage.
Endpoints
Section titled “Endpoints”| Endpoint Role | Discovery Source | Purpose |
|---|---|---|
| Credential Issuer Metadata Endpoint | /.well-known/openid-credential-issuer/{issuer-path} |
Discover issuer metadata, credential configurations, and endpoint URLs |
| Authorization Server Metadata Endpoint | /.well-known/oauth-authorization-server/{issuer-path} (RFC 8414) |
Discover authorization_endpoint, token_endpoint, and supported grant/auth methods for authorization_code |
| Credential Offer URI | credential_offer_uri |
Retrieve a credential offer by reference |
| Token Endpoint | Issuer/Authorization Server metadata | Exchange pre-authorized_code or authorization_code for access token and nonce; accepts client attestation headers |
| Nonce Endpoint | nonce_endpoint from issuer metadata |
Request a fresh c_nonce challenge when needed |
| Credential Endpoint | credential_endpoint from issuer metadata |
Submit proof and request credential issuance |
| Deferred Credential Endpoint | deferred_credential_endpoint from issuer metadata |
Poll a pending issuance transaction with transaction_id |
Illustrative Examples (Non-Normative)
Section titled “Illustrative Examples (Non-Normative)”- Pre-authorized token request body (form-encoded):
grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code&pre-authorized_code=... - Proof JWT protected header for the default mDL
mso_mdoc:{"typ":"openid4vci-proof+jwt","alg":"ES256","kid":"..."}with an EC P-256cnf.jwk(the device key bound into the MSO). The SD-JWT VC and JOSE formats accept any wallet-supported signing algorithm. - Deferred credential response shape:
{"transaction_id":"...","c_nonce":"...","c_nonce_expires_in":...}
Default credential format (behavioural change)
Section titled “Default credential format (behavioural change)”As of the parity-then-flip change, the issuer’s default and lead credential configuration is the ISO/IEC 18013-5 mobile driving licence (mso_mdoc, configuration ID MobileDrivingLicenceMsoMdoc). This was previously the SD-JWT VC university-degree configuration.
What changed: a request that omits an explicit credential configuration (an offer with no credential_configuration_ids, or a credential request that relies on the issuer default) now receives the mDL mso_mdoc instead of the SD-JWT VC.
What did not change: explicit format selection behaves exactly as before. SD-JWT VC and the W3C formats remain fully supported and selectable by naming their configuration:
- SD-JWT VC:
credential_configuration_ids: ["UniversityDegreeCredential"] - JWT VC JSON:
credential_configuration_ids: ["UniversityDegreeCredentialJWT"] - JWT VC JSON-LD:
credential_configuration_ids: ["UniversityDegreeCredentialJWTLD"] - LDP VC:
credential_configuration_ids: ["UniversityDegreeCredentialLDP"]
Action for consumers: API consumers that relied on the implicit SD-JWT VC default must now name UniversityDegreeCredential (or the desired configuration) explicitly to continue receiving SD-JWT VC. The wallet harness honours the WALLET_DEFAULT_CREDENTIAL_FORMAT environment variable (default mso_mdoc) for its stored and presented format.
Note that the mDL proof uses an EC P-256 device key signed with ES256: the proof cnf.jwk is bound into the MSO deviceKeyInfo.deviceKey as a COSE_Key, per ISO/IEC 18013-5.
What To Validate
Section titled “What To Validate”- Credential offer envelope: exactly one of
credential_offerorcredential_offer_uri - Metadata retrieval: well-known endpoint resolves to issuer metadata and endpoint URLs
- Token exchange:
pre-authorized_codegrant processing andtx_codeenforcement when offered - Proof JWT:
typ=openid4vci-proof+jwt,cnf.jwkkey binding,audmatches issuer,noncematches activec_nonce - Nonce behavior: replayed or expired
c_nonceis rejected; next nonce is returned after successful issuance - Credential response: serialization matches negotiated format (e.g.
dc+sd-jwtincludes issuer-signed JWT + disclosures,ldp_vcincludes Data Integrity proof,mso_mdocis base64url-encoded CBORIssuerSignedwith a COSE_Sign1IssuerAuth) - Deferred flow:
issuance_pendingbefore ready time, then final credential on successful poll - Authorization Server metadata:
authorization_endpoint/token_endpointresolve and accept the discoveredauthorization_codegrant - Client attestation (when configured): a request presenting
OAuth-Client-Attestation/-PoPis authenticated by that mechanism, not silently downgraded on failure; replayed PoPjtiis rejected - Key attestation (when a configuration requires it): credential requests without a valid
key_attestationproof header are rejected withinvalid_proof - Encrypted responses (when requested):
credential_response_encryptionis validated against advertisedalg/encvalues and the response is returned as a JWE