Authors:
Reviewers:
Status: Approved
Table of Contents
This specification implements OID4VCI workflow for any issuer as per reference specification [1] (OpenID for Verifiable Credential Issuance - Draft 15). This minimises risks towards interoperability across the European Wallet Ecosystem with a standard specification in the EUDI wallet ecosystem as per the ARF [2] requirements.
The EWC LSP must align with the standard protocol for issuing credentials. This is the basis of interoperability between Issuers and Holders across the EWC LSPs. The assumption is that the user is familiar with the EWC-chosen protocols and standards and can refer to original standards references when necessary.
The OID4VCI specification defines an API for Credential issuance provided by a Credential Issuer, potentially interacting with an Authorization Server. The API comprises the following endpoints:
Issued Credentials SHOULD be bound to the End-User (Holder) possessing them. This allows Verifiers to confirm that the presenter is the legitimate owner.
Cryptographic Binding: Proving control over a private key linked to a public key in the Credential, typically via a proof
in the Credential Request.
The specific Credential(s) being requested/issued are identified at different stages:
credential_configuration_ids
to identify offered configurations listed in its metadata.authorization_details
, the Wallet uses credential_configuration_id
or format
(+ format-specific params) within the openid_credential
object. [4]scope
, the scope value(s) identify the requested configuration(s).authorization_details
was used in the Authorization Request, the AS MUST return authorization_details
containing credential_identifiers
.scope
was used, the AS MAY return credential_identifiers
in authorization_details
.credential_identifiers
were received in the Token Response, the Wallet MUST use the credential_identifier
parameter for each instance.credential_configuration_id
parameter.The credential issuance can use an Authorization Code flow or a Pre-Authorized Code flow.
The standard OAuth 2.0 Authorization Code flow is used, involving redirection via the user agent for authentication and consent.
sequenceDiagram
autonumber
participant W as Wallet (Client/Holder)
participant AS as Authorization Server
participant CI as Credential Issuer (Resource Server)
W->>CI: (1a/1b) Initiate Flow (via Credential Offer or direct interaction)
W->>CI: (2) GET /.well-known/openid-credential-issuer (+ optional AS metadata)
CI-->>W: Issuer Metadata (incl. AS URI, Credential Endpoint)
W->>AS: (3) Authorization Request (via User Agent redirect or PAR)
AS->>W: (User Interaction: Authentication/Consent)
AS-->>W: (4) Authorization Response (with code) (via User Agent redirect)
W->>AS: (5) POST Token Request (incl. code, PKCE verifier)
AS-->>W: Token Response (incl. Access Token, optionally c_nonce, credential_identifiers)
W->>CI: (6) POST Credential Request (incl. Access Token, proof)
CI-->>W: Credential Response (Credential(s) or transaction_id)
Figure 1: Issuance using Authorisation Code Flow based on [1]
This flow bypasses the Authorization Endpoint. The Issuer handles End-User authentication/consent out-of-band and provides a pre-authorized_code
in the Credential Offer. The Wallet exchanges this directly for an Access Token.
sequenceDiagram
autonumber
participant W as Wallet (Client/Holder)
participant CI as Credential Issuer
participant AS as Authorization Server
CI->>W: (1) Credential Offer (incl. pre-authorized_code, optional tx_code info)
W->>CI: (2) GET /.well-known/openid-credential-issuer (+ optional AS metadata)
CI-->>W: Issuer Metadata (incl. AS URI, Token Endpoint, Credential Endpoint)
W->>AS: (3) POST Token Request (incl. pre-authorized_code, optional tx_code, client_id if needed)
AS-->>W: Token Response (incl. Access Token, optionally c_nonce, credential_identifiers)
W->>CI: (4) POST Credential Request (incl. Access Token, proof)
CI-->>W: Credential Response (Credential(s) or transaction_id)
Figure 2: Issuance using Pre-Authorisation Code Flow based on [1]
An optional mechanism for an Issuer to initiate issuance, often via a URI or QR code. The offer can be passed by value (credential_offer
) or by reference (credential_offer_uri
). Passing by reference is RECOMMENDED for QR codes.
The JSON object representing the Credential Offer contains:
Field | Req / Opt | Description |
---|---|---|
credential_issuer |
REQUIRED | URL of the Credential Issuer. Used to fetch Issuer Metadata. |
credential_configuration_ids |
REQUIRED | Array of strings identifying offered configurations from the Issuer’s metadata (credential_configurations_supported map key). |
grants |
OPTIONAL | Object indicating supported Grant Types (authorization_code , urn:ietf:params:oauth:grant-type:pre-authorized_code ). Contains grant-specific parameters (see below). If absent, Wallet determines from AS metadata. |
Grant-Specific Parameters within grants
:
authorization_code
:
| Field | Req / Opt | Description |
| :————————- | :——– | :——————————————————————————————————————————- |
| issuer_state
| OPTIONAL | Opaque string from Issuer to correlate the Authorization Request. If received, Wallet MUST send it in the Authorization Request. |
| authorization_server
| OPTIONAL | Identifier of the AS to use if Issuer metadata lists multiple. MUST match an entry in the authorization_servers
array. |urn:ietf:params:oauth:grant-type:pre-authorized_code
:
| Field | Req / Opt | Description |
| :————————- | :——– | :————————————————————————————————————————————————————————————————————————————————————————– |
| pre-authorized_code
| REQUIRED | Short-lived, single-use code representing Issuer authorization. Wallet MUST send in the Token Request. |
| tx_code
| OPTIONAL | Object describing requirements for a Transaction Code (user PIN/OTP) to prevent replay. If this object is present (even if empty), a tx_code
MUST be sent in the Token Request. See OID4VCI [1] Section 4.1.1 for sub-parameters (input_mode
, length
, description
). |
| interval
| OPTIONAL | Deprecated in draft 15, but previously indicated polling interval for the token endpoint in pending state. |
| authorization_server
| OPTIONAL | Identifier of the AS to use if Issuer metadata lists multiple. MUST match an entry in the authorization_servers
array. |If the offer uses credential_offer_uri
, the Wallet fetches the Credential Offer object.
GET /credential-offer HTTP/1.1
Host: server.example.com
Accept: application/json
The initiating URI often uses a custom scheme:
openid-credential-offer://?credential_offer_uri=https://server.example.com/credential-offer
The Issuer responds with the JSON Credential Offer object.
Example for Authorization Code Flow:
HTTP/1.1 200 OK
Content-Type: application/json
{
"credential_issuer": "https://server.example.com",
"credential_configuration_ids": [
"UniversityDegreeCredential"
],
"grants": {
"authorization_code": {
"issuer_state": "eyJhbGciOiJSU0Et...FYUaBy"
}
}
}
Example for Pre-Authorized Code Flow (with tx_code):
HTTP/1.1 200 OK
Content-Type: application/json
{
"credential_issuer": "https://server.example.com",
"credential_configuration_ids": [
"VerifiablePortableDocumentA1"
],
"grants": {
"urn:ietf:params:oauth:grant-type:pre-authorized_code": {
"pre-authorized_code": "oaKazRN8I0IbtZ0C7JuMn5",
"tx_code": {
"length": 4,
"input_mode": "numeric",
"description": "Please provide the one-time code that was sent via e-mail or offline"
}
}
}
}
[!NOTE] Credential Offers by reference MUST return
application/json
. Credential Offers cannot be signed JWTs. (OID4VCI [1] Section 4.1.3, 4.2).
The Wallet discovers Issuer and AS capabilities via well-known endpoints.
GET {credential_issuer}/.well-known/openid-credential-issuer
GET {authorization_server}/.well-known/oauth-authorization-server
(where {authorization_server}
is from Issuer Metadata or the credential_issuer
URL itself if authorization_servers
is absent).GET /.well-known/openid-credential-issuer HTTP/1.1
Host: server.example.com
Accept-Language: en-GB, en;q=0.9
GET /.well-known/oauth-authorization-server HTTP/1.1
Host: server.example.com (or specific AS host)
Contains Issuer capabilities. Key parameters defined in OID4VCI Section [1] 11.2.3:
Parameter | Req / Opt | Description |
---|---|---|
credential_issuer |
REQUIRED | Issuer identifier URL. |
authorization_servers |
OPTIONAL | Array of AS identifiers used by this Issuer. If omitted, the Issuer acts as its own AS. |
credential_endpoint |
REQUIRED | URL of the Credential Endpoint. |
deferred_credential_endpoint |
OPTIONAL | URL of the Deferred Credential Endpoint. |
nonce_endpoint |
OPTIONAL | URL of the Nonce Endpoint. Required if proofs needing c_nonce are supported. |
notification_endpoint |
OPTIONAL | URL of the Notification Endpoint. |
display |
OPTIONAL | Array of language-specific Issuer display properties (name, locale, logo, etc.). |
credential_configurations_supported |
REQUIRED | Object mapping configuration identifiers (e.g., “VerifiablePortableDocumentA1”) to configuration details. Each configuration detail includes: format (REQUIRED), scope (OPTIONAL), cryptographic_binding_methods_supported (OPTIONAL), credential_signing_alg_values_supported (OPTIONAL), proof_types_supported (OPTIONAL), display (OPTIONAL), Format-specific parameters (REQUIRED/OPTIONAL), and claims (OPTIONAL - Array of claim description objects using path syntax, see OID4VCI Appendix B.2). |
Example for SD-JWT VC Credential:
{
"credential_issuer": "https://server.example.com",
"authorization_servers": [
"https://server.example.com"
],
"credential_endpoint": "https://server.example.com/credential",
"deferred_credential_endpoint": "https://server.example.com/credential_deferred",
"display": [
{
"name": "Example Issuer",
"locale": "en-US"
}
],
"credential_configurations_supported": {
"IdentityCredential": {
"format": "dc+sd-jwt",
"scope": "IdentityCredential",
"cryptographic_binding_methods_supported": [ "jwk" ],
"credential_signing_alg_values_supported": [ "ES256" ],
"proof_types_supported": {
"jwt": {
"proof_signing_alg_values_supported": [ "ES256" ]
}
},
"display": [
{
"name": "Identity Credential",
"locale": "en-US",
"logo": {
"uri": "https://university.example.edu/public/logo.png",
"alt_text": "a square logo of a university"
},
"background_color": "#12107c",
"text_color": "#FFFFFF"
}
],
"vct": "IdentityCredential",
"claims": [
{
"path": ["given_name"],
"display": [
{"name": "Given Name", "locale": "en-US"},
{"name": "Vorname", "locale": "de-DE"}
]
},
{
"path": ["family_name"],
"display": [
{"name": "Surname", "locale": "en-US"},
{"name": "Nachname", "locale": "de-DE"}
]
},
{ "path": ["email"] },
{ "path": ["phone_number"] },
{
"path": ["address"],
"display": [
{"name": "Place of residence", "locale": "en-US"},
{"name": "Wohnsitz", "locale": "de-DE"}
]
},
{ "path": ["address", "street_address"] },
{ "path": ["address", "locality"] },
{ "path": ["address", "region"] },
{ "path": ["address", "country"] },
{ "path": ["birthdate"] }
]
}
}
}
Standard OAuth AS metadata ([RFC8414]), potentially including:
Parameter | Req / Opt | Description |
---|---|---|
issuer |
REQUIRED | Identifier of the Authorization Server. |
authorization_endpoint |
REQUIRED | URL of the AS’s OAuth 2.0 Authorization Endpoint. Required for Auth Code flow. |
token_endpoint |
REQUIRED | URL of the AS’s OAuth 2.0 Token Endpoint. |
jwks_uri |
REQUIRED | URL of the AS’s JSON Web Key Set [JWK] document. Required if using signed responses/tokens. |
pushed_authorization_request_endpoint |
RECOMMENDED | URL of the AS’s Pushed Authorization Request Endpoint. |
require_pushed_authorization_requests |
OPTIONAL | Boolean value specifying whether the AS requires PAR requests. |
scopes_supported |
OPTIONAL | JSON array containing a list of the OAuth 2.0 [RFC6749] scope values that this server supports. |
response_types_supported |
REQUIRED | JSON array containing a list of the OAuth 2.0 response_type values that this server supports. |
token_endpoint_auth_methods_supported |
OPTIONAL | JSON array containing a list of Client Authentication methods supported by this Token Endpoint. |
pre-authorized_grant_anonymous_access_supported |
OPTIONAL | Boolean indicating if Pre-Auth Code flow works without client authentication. Default false . |
{
"issuer": "https://server.example.com",
"authorization_endpoint": "https://server.example.com/authorize",
"pushed_authorization_request_endpoint": "https://server.example.com/par",
"require_pushed_authorization_requests": true,
"token_endpoint": "https://server.example.com/token",
"jwks_uri": "https://server.example.com/.well-known/jwks.json",
"response_types_supported": [
"code",
"vp_token",
"id_token"
],
"subject_types_supported": [
"public",
"pairwise"
],
"id_token_signing_alg_values_supported": [
"ES256"
],
"pre-authorized_grant_anonymous_access_supported": true,
"token_endpoint_auth_methods_supported": [
"none"
]
}
Sent via user agent redirect or PAR (Pushed Authorization Request, RECOMMENDED). Requests access to the Credential Endpoint using either authorization_details
or scope
.
[!NOTE] HAIP[6] chapter 4.2 mandatorily requires Pushed Authorisation Request as per [RFC9126] [7].
The following table describes the query parameters used in the Authorization Request:
Parameter | Req / Opt | Description | Approach |
---|---|---|---|
response_type |
REQUIRED | MUST be code to request an authorization code. Other values like id_token or vp_token might be used for SIOPv2/OpenID4VP interactions during issuance (see Section 6.4). |
Common |
client_id |
REQUIRED | The identifier for the Wallet (client) making the request. | Common |
redirect_uri |
REQUIRED | The redirection endpoint where the Authorization Server will send the User-Agent after authorization is complete. Although marked OPTIONAL in the input, it is REQUIRED by [RFC6749] for the code flow unless pre-registered and only one is registered. | Common |
code_challenge |
REQUIRED | The code challenge used for Proof Key for Code Exchange (PKCE) as specified in [RFC7636] [5]. | Common |
code_challenge_method |
OPTIONAL | The method used to transform the code verifier (S256 or plain ). Defaults to plain . RECOMMENDED to use S256 , as defined in [RFC7636] [5]. |
Common |
state |
RECOMMENDED | Opaque value used by the client to maintain state between the request and callback, providing protection against CSRF. | Common |
scope |
REQUIRED | Specifies the scope of access requested. Used to identify the requested Credential Configuration(s) when not using authorization_details . The value can be consistent across multiple credential configuration objects. The Authorization Server must uniquely identify the Credential Issuer based on the scope value. Scope values in Issuer metadata may overlap with those in the scopes_supported of the AS. |
Scope |
authorization_details |
REQUIRED | JSON array containing detailed authorization requests, including openid_credential type objects. Used instead of scope for fine-grained requests. See [RFC9396] [4]. W3C VC Example: [{"type":"openid_credential", "credential_configuration_id":"...", "credential_definition":{...}}] SD-JWT VC Example: [{"type":"openid_credential", "format":"dc+sd-jwt", "vct":"..."}] |
Authz Details |
resource |
RECOMMENDED | URI identifying the target Resource Server (Credential Issuer). SHOULD be included if the AS protects multiple Issuers (i.e., authorization_servers is present in Issuer metadata). See [RFC8707]. |
Scope (usually) |
issuer_state |
OPTIONAL | A string value representing a specific processing context at the Credential Issuer. Usually provided in a Credential Offer and passed back by the Wallet. | Common |
wallet_issuer |
OPTIONAL | String containing the Wallet’s identifier. RECOMMENDED for Dynamic Credential Requests. | Common |
user_hint |
OPTIONAL | Opaque hint about the End-User. RECOMMENDED for Dynamic Credential Requests. | Common |
authorization_details
Uses the authorization_details
parameter (described in the table above) with one or more objects of type: "openid_credential"
.
GET /authorize?
response_type=code
&client_id=s6BhdRkqt3
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
&authorization_details=[{"type": "openid_credential", "
credential_configuration_id": "VerifiablePortableDocumentA1"}]
&redirect_uri=https://client.example.org/cb
Host: server.example.com
scope
Uses the standard OAuth scope
parameter (described in the table above). Values are typically obtained from the scope
field within the credential_configurations_supported
in Issuer metadata.
authorization_servers
, the resource
parameter containing the Credential Issuer Identifier SHOULD be included.GET https://my-issuer.rocks/auth/authorize?
response_type=code
&scope=VerifiablePortableDocumentA1
&resource=https%3A%2F%2Fcredential-issuer.example.com
&client_id=s6BhdRkqt3
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
Host: server.example.com
Field | Req / Opt | Description |
---|---|---|
issuer_state |
OPTIONAL | Opaque string received in Credential Offer, passed back to Issuer. |
The Authorization Server responds to the Wallet’s Authorization Request. The format depends on whether the request was successful and the requested response_type
.
A successful response redirects the User-Agent back to the client’s redirect_uri
. The parameters included in the redirect depend on the response_type
used in the request:
response_type=code
(Standard Authorization Code Flow):
If standard authorization code flow was requested and successful, the credential issuer redirects the User-Agent to the client’s redirect_uri
with the code
parameter containing the short-lived authorization code. A state
parameter MUST be included if it was present in the request.
Example:
HTTP/1.1 302 Found
Location: https://Wallet.example.org/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=af0ifjsldkj
response_type=id_token
(SIOPv2 Pseudonymous Authentication):
If the credential issuer requests pseudonymous authentication (e.g., DID Authentication) via SIOPv2 [3], the response_type
might be id_token
. Often, response_mode=direct_post
is used, where the AS instructs the Wallet (via the User-Agent redirect) to POST
the resulting id_token
directly to the AS or Issuer’s redirect_uri
.
Example Redirect (Instructing Wallet to POST):
HTTP/1.1 302 Found
Location: {Wallet-Specific-URI}?state=22857405-1a41-4db9-a638-a980484ecae1&client_id=https://example.server.com&redirect_uri=https://example.server.com/direct_post&response_type=id_token&response_mode=direct_post&scope=openid&nonce=a6f24536-b109-4623-a41a-7a9be932bdf6&request_uri=https://example.server.com/request_uri
(Note: The initial part of the Location might be a Wallet-specific custom scheme or universal link)
Query Parameters in the redirect:
Parameter | Description |
---|---|
state |
Opaque value used by the client to maintain state. Included if present in the request. |
client_id |
Identifier for the Credential Issuer acting as the SIOPv2 Relying Party. |
redirect_uri |
The endpoint where the Wallet should POST the id_token . |
response_type |
Indicates id_token is expected. |
response_mode |
Indicates the response delivery method, e.g., direct_post . |
scope |
Must include openid . |
nonce |
String value used to associate a Client session with an ID Token, and to mitigate replay attacks. |
request_uri |
OPTIONAL. This is intended for scenarios where the authorization request is large. The URI can be used by the holder to retrieve the authorization request. |
The holder wallet then generates an id_token
(signed using the Holder’s key, typically derived from a DID) and sends it to the specified redirect_uri
(which acts as the direct post endpoint).
Example Wallet POST:
POST /direct_post HTTP/1.1
Host: example.server.com
Content-Type: application/x-www-form-urlencoded
id_token=eyJraWQiOiJkaW...a980484ecae1&state=22857405-1a41-4db9-a638-a980484ecae1
response_type=vp_token
(Dynamic Credential Request via OpenID4VP):
If the credential issuer requests a presentation during issuance (Dynamic Credential Request) via OpenID4VP, the response_type
includes vp_token
. The AS redirects the User-Agent to the Wallet’s redirect_uri
. This redirect contains parameters that constitute an OpenID4VP Authorization Request, instructing the Wallet what needs to be presented. Refer to the OpenID4VP specification for the exact parameters (like presentation_definition
).
If the request fails, the Authorization Server redirects the User-Agent back to the client’s redirect_uri
with error information encoded in the query parameters, as defined in [RFC6749] Section 4.1.2.1.
Parameter | Description |
---|---|
error |
REQUIRED. A single ASCII error code (e.g., invalid_request , unauthorized_client , access_denied , unsupported_response_type , invalid_scope , server_error , temporarily_unavailable ). |
error_description |
OPTIONAL. Human-readable ASCII text providing additional information. |
error_uri |
OPTIONAL. URI identifying a human-readable web page with information about the error. |
state |
REQUIRED if a state parameter was present in the client authorization request. |
Example:
HTTP/1.1 302 Found
Location: https://Wallet.example.org/cb?error=access_denied&error_description=User%20denied%20access&state=af0ifjsldkj
Exchanges authorization grant (code or pre-authorized code) for tokens.
Standard OAuth 2.0 request ([RFC6749] Section 4.1.3).
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
&grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
&redirect_uri=https%3A%2F%2FWallet.example.org%2Fcb
Uses the urn:ietf:params:oauth:grant-type:pre-authorized_code
grant type.
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
&grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code
&pre-authorized_code=SplxlOBeZQQYbYS6WxSbIA
&tx_code=493536
client_secret_basic
, private_key_jwt
).pre-authorized_grant_anonymous_access_supported: true
, client_id
can be omitted. Otherwise, client authentication is required.Standard OAuth 2.0 response ([RFC6749] Section 5.1) plus OID4VCI extensions.
Field | Req / Opt | Description |
---|---|---|
access_token |
REQUIRED | The Access Token for the Credential Endpoint. |
token_type |
REQUIRED | Typically Bearer . |
expires_in |
RECOMMENDED | Lifetime in seconds. |
refresh_token |
OPTIONAL | Used to obtain new Access Tokens. |
scope |
OPTIONAL | Scope associated with the Access Token. |
c_nonce |
OPTIONAL | Cryptographic nonce to be included in the proof of possession sent to the Credential Endpoint. REQUIRED if the Issuer requires nonce-bound proofs. |
c_nonce_expires_in |
OPTIONAL | Lifetime of the c_nonce in seconds. REQUIRED if c_nonce is present. |
authorization_details |
REQUIRED (if requested with authz_details ) |
Array of objects detailing the granted authorization. [4] For openid_credential type, MUST include: - type : openid_credential - credential_configuration_id : Identifier from metadata - credential_identifiers (REQUIRED): Array of unique strings identifying the specific Credential Dataset instance(s) authorized by this token. Used in Credential Request. |
authorization_details
)HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp..sHQ",
"token_type": "bearer",
"expires_in": 86400,
"c_nonce": "tZignsnFbp",
"c_nonce_expires_in": 86400,
"authorization_details": [
{
"type": "openid_credential",
"credential_configuration_id": "VerifiablePortableDocumentA1",
"credential_identifiers": [ "VerifiablePortableDocumentA1-Spain", "VerifiablePortableDocumentA1-Sweden", "VerifiablePortableDocumentA1-Germany" ]
}
]
}
scope
){
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp..sHQ",
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI4a5k..zEF",
"token_type": "bearer",
"expires_in": 86400,
"id_token": "eyJodHRwOi8vbWF0dHIvdGVuYW50L..3Mz",
"c_nonce": "PAPPf3h9lexTv3WYHZx8ajTe",
"c_nonce_expires_in": 86400
}
Standard OAuth 2.0 errors ([RFC6749] Section 5.2). OID4VCI [1] Section 6.3 provides specific interpretations for Pre-Authorized Code flow:
invalid_request
: tx_code
provided but not expected, or expected but not provided.invalid_grant
: tx_code
expected but wrong value provided; pre-authorized_code
is invalid or expired.invalid_client
: Client authentication required for Pre-Auth flow but not provided or failed.POST
request to the credential_endpoint
using application/json
. Requires Bearer Access Token authorization.
Field | Req / Opt | Description |
---|---|---|
credential_identifier |
REQUIRED (if received in Token Resp.) | String identifying the specific Credential Dataset instance from the credential_identifiers array in the Token Response. Mutually exclusive with credential_configuration_id . |
credential_configuration_id |
REQUIRED (if credential_identifier not used) |
String identifying the Credential Configuration from Issuer metadata. Used when scope was used for authorization and no credential_identifiers were returned. Mutually exclusive with credential_identifier . |
proof |
OPTIONAL (but required if Issuer supports proofs) | Object containing a single proof of possession for the key material the Credential instance should be bound to. MUST contain proof_type . |
Format-specific parameters | Depends on format | May include format-specific request details not covered by credential_configuration_id or credential_identifier . Examples: format , credential_definition , vct . Check OID4VCI [1] Appendix A. |
If the Issuer requires proof of possession (indicated by proof_types_supported
in metadata for the configuration), the proof
parameter MUST be included.
aud
claim in JWT proof).c_nonce
was received in the Token Response, it MUST be included in the proof (nonce
claim in JWT proof).OID4VCI [1] Section 8.2.1 defines:
jwt
: A JWT signed by the key to be bound.
alg
(REQUIRED, asymmetric), typ
(REQUIRED, openid4vci-proof+jwt
), jwk
or kid
(RECOMMENDED). Optional: key_attestation
, trust_chain
.aud
(REQUIRED, Issuer Identifier), iat
(REQUIRED), nonce
(REQUIRED, if c_nonce
received). Optional: iss
(Client ID, if not anonymous Pre-Auth).For W3C VC with credential format identifier jwt_vc_json
:
POST /credential
Content-Type: application/json
Authorization: Bearer eyJ0eXAi...KTjcrDMg
{
"format": "jwt_vc_json",
"credential_definition": {
"type": [
"VerifiableCredential",
"VerifiablePortableDocumentA1"
]
},
"proof": {
"proof_type": "jwt",
"jwt":"eyJraWQiOiJkaWQ6ZX..zM"
}
}
[!NOTE] In the above, the credentialSubject is optional and is not considered within the scope of EWC LSP.
For IETF SD-JWT VC with credential format identifier dc+sd-jwt
:
POST /credential
Content-Type: application/json
Authorization: Bearer eyJ0eXAi...KTjcrDMg
{
"format": "dc+sd-jwt",
"vct": "SD_JWT_VC_example_in_OpenID4VCI",
"proof": {
"proof_type": "jwt",
"jwt":"eyJ0eXAiOiJvc..1WlA"
}
}
Response containing the issued Credential(s) or indicating deferral.
application/json
(unless encrypted).application/jwt
.Field | Req / Opt | Description |
---|---|---|
format |
OPTIONAL | Credential format identifier. May be needed if multiple formats possible for the configuration. (Removed in draft 15, but might be contextually useful). |
credential |
REQUIRED (for immediate single issuance) | The issued Verifiable Credential. Encoding depends on the format (e.g., JWT string, base64url string for binary like mso_mdoc). Mutually exclusive with credentials and transaction_id . |
credentials |
REQUIRED (for immediate batch issuance) | Array of objects, each containing a credential field with one issued Credential instance. Used for batch issuance. Mutually exclusive with credential (singular) and transaction_id . See OID4VCI [1] 8.3. |
transaction_id |
REQUIRED (for deferred issuance) | String identifying the deferred transaction. Used in subsequent requests to the Deferred Credential Endpoint. Mutually exclusive with credential and credentials . |
c_nonce |
OPTIONAL | A new nonce for the Wallet to use in a subsequent Credential Request with the same Access Token. |
c_nonce_expires_in |
OPTIONAL (REQUIRED if c_nonce present) |
Lifetime of the new c_nonce . |
notification_id |
OPTIONAL (if Notification Endpoint supported/used) | String identifying this issuance transaction for later use with the Notification Endpoint. |
{
"credential": "eyJ0eXAiOi...F0YluuK2Cog",
"c_nonce": "fGFF7UkhLa",
"c_nonce_expires_in": 86400
}
Status Code: 202 Accepted
{
"transaction_id": "8xLOxBtZp8",
"c_nonce": "wlbQc6pCJp",
"c_nonce_expires_in": 86400
}
If the Access Token is invalid/expired, return standard OAuth errors ([RFC6750]). For payload errors, use HTTP 400 Bad Request with application/json
body:
Error Code | Description |
---|---|
invalid_request |
Generic request error (SHOULD use more specific codes below). |
invalid_credential_request |
Request malformed, missing required parameter, unsupported parameter, repeated parameter. |
unsupported_credential_type |
Requested Credential Type (e.g., vct ) not supported. |
unsupported_credential_format |
Requested Credential Format not supported. |
invalid_proof |
proof parameter is invalid (missing, signature invalid, required binding info missing like audience/nonce). |
invalid_nonce |
Proof contains an invalid c_nonce value. Wallet should request a new nonce. |
credential_request_denied |
Issuer declined the request for policy reasons. |
Also includes optional error_description
(ASCII text) and error_uri
.
HTTP/1.1 400 Bad Request
Content-Type: application/json
Cache-Control: no-store
{
"error": "invalid_proof",
"error_description": "Proof validation failed: signature mismatch"
}
Optional endpoint (nonce_endpoint
in metadata) to get a fresh c_nonce
.
POST /nonce HTTP/1.1
Host: credential-issuer.example.com
Content-Length: 0
(Requires Access Token if endpoint is protected)
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"c_nonce": "wKI4LT17ac15ES9bw8ac4",
"c_nonce_expires_in": 600
}
Optional endpoint (deferred_credential_endpoint
in metadata) to retrieve a previously deferred credential. Requires the same Access Token as the initial request.
POST /deferred_credential HTTP/1.1
Host: server.example.com
Content-Type: application/json
Authorization: Bearer czZCaGRSa3F0MzpnWDFmQmF0M2JW
{
"transaction_id": "8xL0xBtZp8"
}
If successful, the response is the same as an immediate Credential Response, containing the credential(s)
. HTTP 200 OK.
Uses HTTP 400 Bad Request with application/json
body:
Error Code | Description |
---|---|
invalid_request |
Generic request error. |
invalid_transaction_id |
The transaction_id is invalid, expired, or already used. |
issuance_pending |
Credential issuance is still pending. Response SHOULD include interval (seconds) for Wallet to wait before retrying (default 5s). |
unauthorized_client |
Access token invalid or insufficient. |
HTTP/1.1 400 Bad Request
Content-Type: application/json
Cache-Control: no-store
{
"error": "issuance_pending",
"interval": 30
}
Optional endpoint (notification_endpoint
in metadata) for the Wallet to notify the Issuer about the outcome of an issuance flow. Requires the same Access Token as the issuance request.
POST /notification HTTP/1.1
Host: server.example.com
Content-Type: application/json
Authorization: Bearer czZCaGRSa3F0MzpnWDFmQmF0M2JW
{
"notification_id": "3fwe98js",
"event": "credential_accepted", // or credential_failure, credential_deleted
"event_description": "Credential stored successfully by user." // Optional
}
Field | Req / Opt | Description |
---|---|---|
notification_id |
REQUIRED | String received in the Credential/Deferred Response. |
event |
REQUIRED | String indicating the event type: - credential_accepted : Credential successfully stored. - credential_failure : Storage failed (not due to user deletion). - credential_deleted : User action caused issuance failure/deletion. |
event_description |
OPTIONAL | Human-readable ASCII text with details. |
application/json
:
invalid_notification_id
: ID is invalid.invalid_notification_request
: Request malformed or missing required parameter.HTTP/1.1 204 No Content
Please refer to the implementers table.
Refer RFC012 for key resolution and trust mechanims.
For a JWT (e.g., Request Object, VP Token, ID Token), there are multiple ways for resolving the public key needed for signature verification:
jwk
Header: The public key JWK is directly embedded in the JOSE header.kid
Header: The header contains a Key ID. The key needs to be retrieved:
kid
is a DID URL (e.g., did:example:123#key-1
), use DID resolution to obtain the verification method (public key) from the DID Document.iss
claim), fetch the jwks_uri
from the metadata and find the key matching the kid
in the JWK Set.x5c
Header: The header contains the X.509 certificate chain containing the public key. Validate the chain according to PKI rules.