Skip to main content
European CommissionEBSI European Blockchain

How to issue Verifiable Credentials

Last updated on

Context

This document defines the Verifiable Credential Issuance (VCI) specification in compliance with OpenID for Verifiable Credential Issuance (version: openid-4-verifiable-credential-issuance-1_0-10).

Glossary

TermAbbreviationDescription
Verifiable Credential IssuanceVCIProcess defined in this documentation.
Relying PartyRPOAuth 2.0 compliant client.
OpenID ProviderOPA service that can authenticate the end-user and provide claims to a Relying Party about the Authentication event and the end-user.
Self-issued OpenID ProviderSIOPAn OpenID Provider (OP) used by the end-user to prove control over a cryptographically verifiable identifier. W3C refers to this as a Holder.
IssuerISSA role an entity can perform by asserting claims about one or more subjects, creating a Verifiable Credential from these claims, and transmitting the Verifiable Credential to a holder.
OpenID for Verifiable CredentialsOID4VCOpenID for Verifiable Credentials (OID4VC) defines an API and corresponding OAuth-based authorisation mechanisms for the issuance of Verifiable Credentials.
User AgentUAHolder Wallet or a browser.
Verifiable CredentialVCA set of one or more claims made by an Issuer. A Verifiable Credential (VC) is a tamper-evident credential that has authorship that can be cryptographically verified. VCs can be used to build Verifiable Presentations, which can also be cryptographically verified.
JSON Web Key SetJWKSA data structure that represents a set of JWKs as defined in RFC 7517.
Authorisation ServerASThe Authorisation Server (AS) is responsible for authentication and authorisation on behalf of the Credential Issuer. It can request Clients' ID Tokens, exchange information via Verifiable Presentations, and issue access Tokens.

Overview

The VCI specification is split into two logical boundaries on the provider side, which can be co-located in the same service or distributed as separate services. The logical boundaries are the Authorisation Server and the Credential Issuer. The Authorisation Server handles authentication and authorisation on behalf of the Credential Issuer. The Credential Issuer acts as an OAuth 2.0 protected endpoint for the actual issuance of the VCs.

The Provider's clients can be Holder Wallets or Service Wallets. The Wallets are not callable from the internet; instead, interactions are facilitated through redirects or QR codes. The VCI process can be initiated by the Issuer (using a QR code or redirect) or by the end-user through the Wallet.

End-user initiated flow

Alice (the user) opens her Holder Wallet with the intent of obtaining a digital diploma. She finds her university's Issuer and applies for a diploma (user initiated). The Issuer acknowledges this and asks her to log in with her university credentials (login with the existing method). The Issuer also requests her consent to the creation of a digital diploma (user consent). She confirms and is redirected back to her wallet. There, she is notified of the successful creation of the digital diploma (real-time VC issuance).

Issuer Initiated Flow

Alice browses her university's home page, searching for a way to obtain a digital diploma. She finds the respective page, which shows a link to "request your digital diploma" (Issuer initiated). She clicks on this link and is redirected to her digital wallet. The wallet notifies her that an Issuer has offered to issue a diploma credential. She confirms this inquiry and is redirected to the university's credential issuance service. She logs in with her university credentials (login with the existing method) and is asked to consent to the creation of a digital diploma (user consent). She confirms and is redirected to her wallet. There, she is notified of the successful creation of the digital diploma (real-time VC issuance).

The Issuer-initiated scenario can operate using one of two different interaction models:

  • Same-device model: The user has a Holder wallet installed on the same device used to visit the credential Issuer's website. The Issuer initiates the interaction using an HTTP redirect.

    Example: Alice has her self-sovereign identity (SSI) wallet installed on her smartphone. She uses her smartphone to visit the credential Issuer's website (e.g., the university website where she can request the issuance of a diploma VC).

  • Cross-device model: The user has a Holder wallet installed on a different device than the one used to visit a credential Issuer's website. The Issuer initiates the interaction by providing a QR code, which the user must scan using the device that contains the Holder Wallet.

    Example: Alice has her SSI wallet installed on her smartphone and visits the credential Issuer's website (e.g., the university website where she can request issuance of a diploma VC) on her laptop. After she clicks on "Issue Credentials", a QR code is presented, and Alice scans the QR code using her SSI wallet app on her smartphone.

HTTP Redirect and QR code conDtent can be the same, but the QR code content should be minimalistic due to the data size limitations.

User authentication

Users can authenticate through various methods:

  • Using the Issuer's existing authentication service.
  • Demonstrating control of a decentralised identifier (DID) when the Issuer already has a pre-established relationship with the DID.
  • Presenting a VC recognised by the Issuer, which can serve as a means of authentication.
  • Employing a combination of the above methods.
ID Token

Note that functional flows currently use forced ID Token requests and responses to authenticate the user. This is done to cover some authentication in conformance testing. This may be removed if the user does not need to be authenticated with a DID, or if other authentication means are used.

The end-user DID for the VC is presented during the Credential Request flow, in the JWT proof.

Example of Alice digital diploma authentication

Alice enters the university website and applies for a digital diploma. The diploma Issuer has established an elaborate authentication scheme, which requires a VC asserting Alice's Holder Wallet qualities and end-user authentication with a username and password in their own OIDC-capable authentication system. After the wallet and the user has been authenticated, the Authorisation Server responds with an Authorisation Response, which can be exchanged for an Access Token.

The example shows only the authentication part, which happens after the Credential Offering has been received by the Holder and the Holder has completed discovery.

Client authentication

Service Clients must use a signed Request Object to ensure authenticity, integrity and non-repudiation of the request content. Service Wallets must expose the used keys as a JWKS, which can be referenced through Service Wallet Metadata.

The Request Object follows the OpenID Connect Core but must be signed by the Relying Party (Client).

Holder Wallets must use Proof Key for Code Exchange (PKCE) - RFC 7636 when applying for code flow.

Credential Offering

Issuers may want to initiate the Credential Offering process. To facilitate this, wallets must implement the openid-credential-offer "endpoint". The wallet's endpoint will begin the flow (code or pre-authorized) if the issuer is deemed trustworthy. The credential offering serves only as an instruction for the wallet. It is recommended to seek consent from the user before proceeding with the offering or deciding against it.

The Credential Offering can be provided as a value, where the data structure below is added under the credential_offer field, or as a URI reference through the credential_offer_uri field, which will resolve to the same data structure with a content type of application/json. It is recommended to serve the Credential Offering under the same domain as the Credential Issuer.

Note: As this is a wallet endpoint/schema handler, the wallet does not generate any response and retains control of the user experience within the wallet itself. This can also be accessed through a QR code.

ParameterRequirementDescription
credential_issuerREQUIREDThe URL of the Credential Issuer from which the Wallet is requested to obtain one or more credentials.
credentialsREQUIREDA JSON array where every entry is a JSON object containing the data related to a certain credential type the Wallet MAY request.
credentials
.format
REQUIREDA JSON string identifying the format of this credential, e.g., jwt_vc.
credentials
.types
REQUIREDA JSON array designating the types a certain credential type supports according to VC_DATA, Section 4.3.
credentials
.trust_framework
OPTIONALA JSON object defining the Trust Framework details.
credentials
.trust_framework
.name
REQUIREDA JSON string defining the Trust Framework name the credential will be issued under.
credentials
.trust_framework
.type
REQUIREDA JSON string designating the behaviour and properties bound to the type. Types may include extra properties.
credentials
.trust_framework
.uri
OPTIONALA JSON string associated with the type field, having the value of Accreditation, which points to the Verifiable Accreditation that serves as the basis for the issuance of the VC.
grantsOPTIONALA JSON object indicating to the Wallet the Grant Types the Credential Issuer's AS is prepared to process for this credential offer. Each grant is represented by a key and an object. The key value is the Grant Type identifier, and the object MAY contain parameters that either dictate the way the Wallet MUST use the particular grant and/or parameters the Wallet MUST send with the respective request(s). If the grants field is not present or empty, the Wallet MUST determine the Grant Types supported by the Credential Issuer's AS using the respective metadata. When multiple grants are present, it is at the Wallets discretion to decide which one to use.
grants
.authorization_code
OPTIONALA JSON object for authorisation code flow.
grants
.authorization_code
.issuer_state
OPTIONALA string value created by the Credential Issuer and opaque to the Wallet that is used to bind the subsequent Authorisation Request with the Credential Issuer to a context set up during previous steps. If the Wallet decides to use the Authorisation Code Flow and receives a value for this parameter, it MUST include it in the subsequent Authorisation Request to the Credential Issuer as the issuer_state parameter value.
grants
.urn:ietf:params:oauth:grant-type:pre-authorized_code
OPTIONALA JSON object for the pre-authorised flow.
grants
.urn:ietf:params:oauth:grant-type:pre-authorized_code
.pre-authorized_code
REQUIREDThe code representing the Credential Issuer's authorisation for the Wallet to obtain Credentials of a certain type. This code MUST be short lived and single use. If the Wallet decides to use the Pre-Authorised Code Flow, this parameter value MUST be include in the subsequent Token Request with the Pre-Authorised Code Flow.
grants
.urn:ietf:params:oauth:grant-type:pre-authorized_code
.user_pin_required
OPTIONALA boolean value specifying whether the Credential Issuer expects the presentation of a user PIN along with the Token Request in a Pre-Authorised Code Flow. The default value is false. This PIN is intended to bind the Pre-Authorized Code to a certain transaction to prevent the replay of this code by an attacker that, for example, scanned the QR code while standing behind the legitimate user. It is RECOMMENDED to send a PIN via a separate channel. If the Wallet decides to use the Pre-Authorised Code Flow, a PIN value MUST be sent in the user_pin parameter with the respective Token Request.
Credential Offering - Non-normative example
HTTP 302 Location: openid-credential-offer://
?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fapi-conformance.ebsi.eu%2Fconformance%2Fv4%2Fissuer-mock%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22jwt_vc%22%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22VerifiableAttestation%22%2C%22CTWalletInTime%22%5D%2C%22trust_framework%22%3A%7B%22name%22%3A%22EBSI%22%2C%22type%22%3A%22Accreditation%22%2C%22id%22%3A%22https%3A%2F%2Fapi-pilot.ebsi.eu%2Ftrusted-issuers-registry%2Fv4%2Fissuers%2Fdid%3Aebsi%3AzZeKyEJfUTGwajhNyNX928z%2Fattributes%2F60ae46e4fe9adffe0bc83c5e5be825eafe6b5246676398cd1ac36b8999e058aa%22%7D%7D%5D%2C%22grants%22%3A%7B%22authorization_code%22%3A%7B%22issuer_state%22%3A%22tracker%3Dvcfghhj%22%7D%7D%7D
Credential Offering as URI reference - Non-normative example
HTTP 302 Location: openid-credential-offer://
?credential_offer_uri=https%3A%2F%2Fapi-conformance.ebsi.eu%2Fconformance%2Fv4%2Fissuer-mock%2Foffers%2Fzyx

Pre-Authorised Code Flow

The Issuer can also bypass authentication and use the pre-authorised flow. This method is intended for use cases where the Issuer's website enables one or more credentials to be shared, and the user is already authenticated with the website. Pre-authorisation employs Credential Offering capabilities but requires pre-authorized_code and may optionally use the user_pin_required parameter. The user PIN code must be delivered through other channels, as the QR code or request could be intercepted. It is recommended to use a PIN code with the pre-authorised flow.

The PIN code and pre-authorised code must be delivered to the Token Endpoint to be exchanged for an Access Token.

Pre-authorised Credential Offering - Non-normative example
HTTP 302 Location: openid-credential-offer://
?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fapi-conformance.ebsi.eu%2Fconformance%2Fv4%2Fissuer-mock%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22jwt_vc%22%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22VerifiableAttestation%22%2C%22CTWalletInTime%22%5D%2C%22trust_framework%22%3A%7B%22name%22%3A%22EBSI%22%2C%22type%22%3A%22Accreditation%22%2C%22id%22%3A%22https%3A%2F%2Fapi-pilot.ebsi.eu%2Ftrusted-issuers-registry%2Fv4%2Fissuers%2Fdid%3Aebsi%3AzZeKyEJfUTGwajhNyNX928z%2Fattributes%2F60ae46e4fe9adffe0bc83c5e5be825eafe6b5246676398cd1ac36b8999e058aa%22%7D%7D%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22SplxlOBeZQQYbYS6WxSbIA%22%2C%22user_pin_required%22%3Atrue%7D%7D%7D

Authorisation Server service

The Authorisation Server is responsible for authentication and authorisation on behalf of the Credential Issuer. The Authorisation Server can request Clients' ID Tokens, exchange information via Verifiable Presentations and issue access Tokens.

Metadata configuration

Please see the Credential Issuer Metadata specifications for further details.

Authorisation Request

This call is from the Client to the Authorisation Server.

The Authorisation Request builds on the OAuth 2.0 Rich Authorisation Request, where the user specifies which types of VCs they are requesting using the authorization_details parameter. The full Authorisation Request is described in the table below.

The Authorisation Request must support a Request Object, which is signed by the Relying Party. A Holder Wallet acting as a Relying Party should only use PKCE.

Authorisation Request content:

ParameterDescription
response_typeValue MUST be set to code.
client_idREQUIRED. HTTPS URL for Service Wallets and DID for Holder Wallets.
redirect_uriREQUIRED. Fully qualified domain name (FQDN) for redirection of the response.
scopeREQUIRED. MUST contain "openid".
issuer_stateCONDITIONAL: REQUIRED if Credential Offering contains issuer_state.
stateRECOMMENDED. An opaque value used by the client to maintain state between the request and callback. The Authorisation Server includes this value when redirecting the user-agent back to the client. This parameter SHOULD be used for preventing cross-site request forgery (CSRF).
authorization_detailsREQUIRED. One of many Authorisation Details objects below.
authorization_details
.type
Determines the authorisation details type. MUST be set to openid_credential for the purpose of this specification.
authorization_details
.locations
CONDITIONAL. If the Credential Issuer metadata contains an authorization_server parameter, the field MUST be set to the Credential Issuer's client_id value, which allows the AS to mint audience restricted access tokens.
authorization_details
.format
The format in which the credential is requested to be issued. Valid values defined by this specification are jwt_vc and ldp_vc. Profiles of this specification MAY define additional format values.
authorization_details
.types
A JSON array designating the types a certain credential type supports according to VC_DATA, Section 4.3.
nonceRECOMMENDED. A value used to associate a Client session with an ID Token and mitigate replay attacks.
code_challenge

CONDITIONAL: Only for Holder Wallets. In the format of BASE64URL-ENCODE(SHA256(code_verifier as UTF-8 string)).

code_verifier is a client-generated secure random string used with the token endpoint. It is between 43 and 128 characters long and contains characters A-Z, a-z, 0-9, hyphen, period, underscore and tilde. Please see RFC 7636.
code_challenge_methodCONDITIONAL: Only for Holder Wallets. If the client can use the "S256" method, it MUST use "S256". Otherwise, "plain" can only be used if "S256" is not supported.
client_metadataCONDITIONAL: Only for Holder and Service Wallets. Overwrites the defaults defined in the Holder Wallet Metadata. The object structure matches the metadata structure.

Authorisation Request Non-normative examples

Service Wallet: Signed Authorisation Request
GET from https://api-conformance.ebsi.eu/conformance/v3/auth-mock/authorize?
client_id=https%3A%2F%2Fmy-issuer.eu%2Fsuffix%2Fxyz
&response_type=code
&scope=openid
&redirect_uri=https%3A%2F%2Fmy-issuer.eu%2Fsuffix%2Fxyz%2Fcode-cb
&request=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IkpwLTliRUk4dTY4LWU5NW1Ud25DRktLSWdUbkR1Ukhjb1RIYXpwWENKdDQifQ.eyJpc3MiOiJodHRwczovL215LWlzc3Vlci5ldS9zdWZmaXgveHl6IiwiYXVkIjoiaHR0cHM6Ly9hcGktY29uZm9ybWFuY2UuZWJzaS5ldS9jb25mb3JtYW5jZS92My9hdXRoLW1vY2siLCJleHAiOjE1ODk2OTg4MDAsInJlc3BvbnNlX3R5cGUiOiJjb2RlIiwic2NvcGUiOiJvcGVuaWQiLCJub25jZSI6Imdsa0ZGb2lzZGZFdWk0MyIsImNsaWVudF9pZCI6Imh0dHBzOi8vbXktaXNzdWVyLmV1L3N1ZmZpeC94eXoiLCJhdXRob3JpemF0aW9uX2RldGFpbHMiOlt7InR5cGUiOiJvcGVuaWRfY3JlZGVudGlhbCIsImZvcm1hdCI6Imp3dF92YyIsInR5cGVzIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUF1dGhvcmlzYXRpb25Ub09uYm9hcmQiXX1dLCJyZWRpcmVjdF91cmkiOiJodHRwczovL215LWlzc3Vlci5ldS9zdWZmaXgveHl6L2NvZGUtY2IiLCJjbGllbnRfbWV0YWRhdGEiOnsiandrc191cmkiOiJodHRwczovL215LWlzc3Vlci5ldS9zdWZmaXgveHl6L2p3a3MiLCJhdXRob3JpemF0aW9uX2VuZHBvaW50Ijoib3BlbmlkOiJ9fQ.oYltoDJlhVFlww7D0QsbSGmfWt2_mRbLg-5DYccfPphc8mYW4oGMz4YjGLXokGcfwdB-jMIAzEkkQATMSq4kmQ

JWT Header:
{
typ: 'JWT',
alg: 'ES256',
kid: 'Jp-9bEI8u68-e95mTwnCFKKIgTnDuRHcoTHazpXCJt4'
}
JWT Payload:
{
iss: 'https://my-issuer.eu/suffix/xyz',
aud: 'https://api-conformance.ebsi.eu/conformance/v3/auth-mock',
exp: 1589698800,
response_type: 'code',
scope: 'openid',
nonce: 'glkFFoisdfEui43',
client_id: 'https://my-issuer.eu/suffix/xyz',
authorization_details: [
{
type: 'openid_credential',
format: 'jwt_vc',
locations: ['https://api-conformance.ebsi.eu/conformance/v3/issuer-mock'],
types: [
'VerifiableCredential',
'VerifiableAttestation',
'VerifiableAuthorisationToOnboard'
]
}
],
redirect_uri: 'https://my-issuer.eu/suffix/xyz/code-cb',
client_metadata: {
jwks_uri: 'https://my-issuer.eu/suffix/xyz/jwks',
authorization_endpoint: 'openid:'
}
}
Holder Wallet: Plain Authorisation Request
GET from https://api-conformance.ebsi.eu/conformance/v3/auth-mock/authorize
?response_type=code
&scope=openid
&issuer_state=tracker%3Dvcfghhj
&state=af0ifjsldkj
&client_id=did%3Akey%3Az2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r
&authorization_details=%5B%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22jwt_vc%22%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22VerifiableAttestation%22%2C%22CTWalletInTime%22%5D%7D%5D
&redirect_uri=openid%3A
&nonce=glkFFoisdfEui43
&code_challenge=YjI0ZTQ4NTBhMzJmMmZhNjZkZDFkYzVhNzlhNGMyZDdjZDlkMTM4YTY4NjcyMTA5M2Q2OWQ3YjNjOGJlZDBlMSAgLQo%3D
&code_challenge_method=S256
&client_metadata=%7B%22vp_formats_supported%22%3A%7B%22jwt_vp%22%3A%7B%22alg%22%3A%5B%22ES256%22%5D%7D%2C%22jwt_vc%22%3A%7B%22alg%22%3A%5B%22ES256%22%5D%7D%7D%2C%22response_types_supported%22%3A%5B%22vp_token%22%2C%22id_token%22%5D%2C%22authorization_endpoint%22%3A%22openid%3A%22%7D

ID Token Request

ID Token

Other User Authentication methods can be used instead of an ID Token, and it may be fully omitted if the DID is not needed for authorisation purposes. The ID Token is used only to authenticate user through proof of control, and only for authorisation purposes.

This call is from the Authorisation Server to the Client and is always a redirect to the Client's defined authorization_endpoint, which must be provided in the client_metadata field of the initial Authorisation Request. The default value is openid:.

The ID Token Request must always use a signed Request Object. Any other User Authentication method can be used instead of the ID Token Request.

ID Token Request
HTTP 302 Location: openid://
?client_id=https%3A%2F%2Fapi-conformance.ebsi.eu%2Fconformance%2Fv4%2Fauth-mock
&response_type=id_token
&scope=openid
&redirect_uri=https%3A%2F%2Fapi-conformance.ebsi.eu%2Fconformance%2Fv4%2Fauth-mock%2Fdirect_post
&request=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImM0S3JlcEpYem1CTVctcW8ybnREQ3drVGdMbTJDYl81ZWFiemtsalRoXzAifQ.eyJpc3MiOiJodHRwczovL2FwaS1jb25mb3JtYW5jZS5lYnNpLmV1L2NvbmZvcm1hbmNlL3YzL2F1dGgtbW9jayIsImF1ZCI6Imh0dHBzOi8vbXktaXNzdWVyLmV1L3N1ZmZpeC94eXoiLCJleHAiOjE1ODk2OTkxNjIsInJlc3BvbnNlX3R5cGUiOiJpZF90b2tlbiIsInJlc3BvbnNlX21vZGUiOiJkaXJlY3RfcG9zdCIsImNsaWVudF9pZCI6Imh0dHBzOi8vYXBpLWNvbmZvcm1hbmNlLmVic2kuZXUvY29uZm9ybWFuY2UvdjMvYXV0aC1tb2NrIiwicmVkaXJlY3RfdXJpIjoiaHR0cHM6Ly9hcGktY29uZm9ybWFuY2UuZWJzaS5ldS9jb25mb3JtYW5jZS92My9hdXRoLW1vY2svZGlyZWN0X3Bvc3QiLCJzY29wZSI6Im9wZW5pZCIsInN0YXRlIjoiNDhhMmJhYzYtMTMwYS00Mzc4LWJjYzItMDRlYjU3YzU0M2I5Iiwibm9uY2UiOiJuLTBTNl9XekEyTWoifQ.d-3D3w99BvRq_N1tmUaDwlaG8oGnOiA4mVs1Cgp1USc1Yhf8TN9G8Vt_SO_LmJGspuqST8ESwUUkmYvXOYj5Pw

JWT Header:
{
typ: 'JWT',
alg: 'ES256',
kid: 'c4KrepJXzmBMW-qo2ntDCwkTgLm2Cb_5eabzkljTh_0'
}
JWT Payload:
{
iss: 'https://api-conformance.ebsi.eu/conformance/v3/auth-mock',
aud: 'https://my-issuer.eu/suffix/xyz',
exp: 1589699162,
response_type: 'id_token',
response_mode: 'direct_post',
client_id: 'https://api-conformance.ebsi.eu/conformance/v3/auth-mock',
redirect_uri: 'https://api-conformance.ebsi.eu/conformance/v3/auth-mock/direct_post',
scope: 'openid',
state: '48a2bac6-130a-4378-bcc2-04eb57c543b9',
nonce: 'n-0S6_WzA2Mj'
}

ID Token Response

This call is from the Client to the Authorisation Server and is signed using the DID controlled keys.

The response mode direct_post is derived from the ID Token Request's content. The redirect_uri serves as the POST call endpoint, and the nonce must be included in the subject-signed ID Token.

The state parameter is mandatory for the ID Token Response when it is present in the ID Token Request sent by the Authorisation Server. In such cases, the Client must ensure that the state parameter values are identical in both.

caution

Please note that the ID Token Response's state is different from the Authorisation Request's state.

The Auth Mock server responds to the /auth-mock/authorize request with an ID Token request that has a new random state. It does not reuse the state from the initial Authorisation request.

The main purpose of the subject-signed ID Token is to prove control of a DID, while the secondary purpose is to transfer user-declared parameters, such as a preferred email address. The ID Token is purely meant for authentication, while the authorisation might contain other DIDs.

ID Token Response
POST into https://api-conformance.ebsi.eu/conformance/v3/auth-mock/direct_post
Content-Type: application/x-www-form-urlencoded

id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDplYnNpOnpkUGoxR1BYamZFUlh4WFBFMVlUWWRKIzdqM1RwYU5kUE5UT3pPdG91T09rbmxPTFFrM0pQLXlrVGZyYVd0WTNHTUUifQ.eyJpc3MiOiJkaWQ6ZWJzaTp6ZFBqMUdQWGpmRVJYeFhQRTFZVFlkSiIsInN1YiI6ImRpZDplYnNpOnpkUGoxR1BYamZFUlh4WFBFMVlUWWRKIiwiYXVkIjoiaHR0cHM6Ly9hcGktY29uZm9ybWFuY2UuZWJzaS5ldS9jb25mb3JtYW5jZS92My9hdXRoLW1vY2siLCJleHAiOjE1ODk2OTkzNjAsImlhdCI6MTU4OTY5OTI2MCwic3RhdGUiOiI0OGEyYmFjNi0xMzBhLTQzNzgtYmNjMi0wNGViNTdjNTQzYjkiLCJub25jZSI6Im4tMFM2X1d6QTJNaiJ9.NjkSEc6RXbcALTSTV5kHL-KdBXPzWTdvEPxUgjSuSqASWQpsK0i2PxDcpSAMwcXNoYY-Gq15R7ui97YFBZYN7Q
&state=48a2bac6-130a-4378-bcc2-04eb57c543b9

JWT Header:
{
typ: 'JWT',
alg: 'ES256',
kid: 'did:ebsi:zdPj1GPXjfERXxXPE1YTYdJ#7j3TpaNdPNTOzOtouOOknlOLQk3JP-ykTfraWtY3GME'
}
JWT Payload:
{
iss: 'did:ebsi:zdPj1GPXjfERXxXPE1YTYdJ',
sub: 'did:ebsi:zdPj1GPXjfERXxXPE1YTYdJ',
aud: 'https://api-conformance.ebsi.eu/conformance/v3/auth-mock',
exp: 1589699360,
iat: 1589699260,
nonce: 'n-0S6_WzA2Mj'
}

The state parameter management

The following sequence diagram illustrates the management of the state parameter between the Wallet and the Authorisation Server.

Authorisation Response

After successful authentication and authorisation, the Authorisation Response may be returned to the client.

The Authorisation Response builds upon the OID4VC Authentication Response defined in the OID4VC Authentication Response.

The Authorisation Response schema is defined in the following table:

ParameterDescription
redirect_uriREQUIRED. Redirection URI to the wallet.
codeREQUIRED. A query parameter in the URI.
stateREQUIRED. A query parameter in the URI.
Authorization Response - Non-normative example
HTTP/1.1 302 Found
Location: <redirect_uri>?
code=SplxlOBeZQQYbYS6WxSbIA
&state=af0ifjsldkj

Note: The Client MUST check that the state value matches the state value from the Authorisation Request to prevent CSRF.

Token Request

An authorisation code (from the Authorisation Response) is exchanged for an Access Token and an ID Token, both of which are signed by the Authorisation Server. Service Wallet client authentications are performed using client_assertion with a client_assertion_type of urn:ietf:params:oauth:client-assertion-type:jwt-bearer, while Holder Wallets are authenticated with PKCE's code_verifier.

The Token Request schema is defined in the following table:

ParameterDescription
grant_typeREQUIRED. MUST be authorisation_code or pre-authorised_code.
client_idREQUIRED. HTTPS URL for Service Wallets and DID for Holder Wallets.
codeREQUIRED. MUST be the Authorisation code from the Authorisation Response.
redirect_uriREQUIRED. Redirection URL to the wallet.
pre-authorised_code

CONDITIONAL & REQUIRED. Only used with pre-authorised_code. Code received in the Pre-Authorisation flow.

code parameter cannot co-exist in the same request.
user_pinCONDITIONAL & OPTIONAL. Only used with pre-authorised_code. Maximum of 8 numbers (0-9).
client_assertionOPTIONAL. A minimal self-signed JWT for jwt-bearer.
client_assertion_typeOPTIONAL. MUST be urn:ietf:params:oauth:client-assertion-type:jwt-bearer.
code_verifierOPTIONAL. Wallet generated secure random token, used to validate the original code_challenge provided in initial Authorisation Request.
Token Request - Non-normative example
POST into https://api-conformance.ebsi.eu/conformance/v3/auth-mock/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&client_id=https%3A%2F%2Fmy-issuer.eu%2Fsuffix%2Fxyz
&code=SplxlOBeZQQYbYS6WxSbIA
&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
&client_assertion=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IkpwLTliRUk4dTY4LWU5NW1Ud25DRktLSWdUbkR1Ukhjb1RIYXpwWENKdDQifQ.eyJpc3MiOiJodHRwczovL215LWlzc3Vlci5ldS9zdWZmaXgveHl6Iiwic3ViIjoiaHR0cHM6Ly9teS1pc3N1ZXIuZXUvc3VmZml4L3h5eiIsImF1ZCI6Imh0dHBzOi8vYXBpLWNvbmZvcm1hbmNlLmVic2kuZXUvY29uZm9ybWFuY2UvdjMvYXV0aC1tb2NrIiwianRpIjoiZjJlODFjOTYtZTA4ZS00MzhlLTk1YmMtOGI3ZDA5ZGI2Y2IxIiwiZXhwIjoxNTg5Njk5NjYwLCJpYXQiOjE1ODk2OTk1NjB9.ep0iemeZGl4vPUKXGx7GP1pkE6GiJ8tZyChTHrztjj_641omRE_pC1n43dwYSPNnMknQfsndOWHjaKLmI7qTGg

JWT Header:
{
typ: 'JWT',
alg: 'ES256',
kid: 'Jp-9bEI8u68-e95mTwnCFKKIgTnDuRHcoTHazpXCJt4'
}
JWT Payload:
{
iss: 'https://my-issuer.eu/suffix/xyz',
sub: 'https://my-issuer.eu/suffix/xyz',
aud: 'https://api-conformance.ebsi.eu/conformance/v3/auth-mock',
jti: 'f2e81c96-e08e-438e-95bc-8b7d09db6cb1',
exp: 1589699660,
iat: 1589699560
}

Token Response

In addition to an access_token and id_token, the response contains a challenge nonce c_nonce to be used with the Credential Issuer. The specification does not define whether the Authorisation Service should continue issuing new c_nonces or not, but it is recommended to deliver the first c_nonce within the access_token and allow the Credential Issuer to manage subsequent c_nonce attributes as needed.

The Token Response schema is defined in the following table:

ParameterDescription
access_tokenREQUIRED. The token is later used for the issuance of the actual Verifiable Credential.
id_tokenREQUIRED. The token that contains the user's identifier as Subject Identifier.
token_typeREQUIRED. Value of the token type MUST be bearer.
expires_inREQUIRED. Value denoting the lifetime in seconds of the token.
c_nonceREQUIRED. A string containing a random challenge that needs to be signed to create a proof of possession of key material when requesting the actual Verifiable Credential. This value MUST be random and used only once.
c_nonce_expires_inOPTIONAL. Value denoting the lifetime in seconds of the c_nonce.
Token Response - Non-normative example
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp..sHQ",
"token_type": "bearer",
"expires_in": 86400,
"id_token": "eyJodHRwOi8vbWF0dHIvdGVuYW50L..3Mz",
"c_nonce": "PAPPf3h9lexTv3WYHZx8ajTe",
"c_nonce_expires_in": 86400
}

Credential Issuer service

The Credential Issuer service is an OAuth 2.0 protected service with two endpoints. The first endpoint is the Credentials Endpoint, which encompasses proofs, in-time issuance and the option to defer the issuance. The second endpoint is the Deferred Credentials Endpoint, which manages asynchronous issuance through polling.

Credential Request

The Credential Request is sent to the Credential Issuer's Credentials Endpoint, as defined in the metadata. It initiates the issuance of the VC for the credentials specified in the initial Authorisation Request.

Multiple entires

The endpoint may be called multiple times with different proofs of DID control, leading to multiple VCs for different DIDs but for the same identity/authority. This behaviour is the default and must be restricted by the Issuer if needed.

The Credential Request schema is defined in the following table:

ParameterValueDescription
typesstringREQUIRED. The type definition, identifying the Verifiable Credential.
formatstringOPTIONAL. The format in which the Verifiable Credential should be issued. It MUST be jwt_vc or ldp_vc.
proofProof objectCONDITIONAL. An object containing proof of possession of the key material. The proof is generated based on the c_nonce from Token Response.

Proof

Proof is mandatory for VCs that are bound to DIDs. The target DID for the VC is delivered in the header kid, which is a DID URI containing the DID and optionally the key ID.

ID Token

Note that an ID Token is only used for authentication purposes and is separate from the DID to which the VC should be bound. It is upto business, domain or trust framework to determine whether proof of DID control is sufficient to allow any DID for the VC, or if the DID must be known before the Credential Request.

ParameterValueDescription
proof_typestringREQUIRED. JSON String denoting the proof type. It MUST be "jwt".
jwtSigned JWTCONDITIONAL, when proof_type is jwt. Objects of this type contain a single jwt element with a signed JWT as proof of possession.

The signed JWT proof must contain the following parameters:

LocationParameterValueDescription
HeadertypstringREQUIRED. Must be openid4vci-proof+jwt.
HeaderalgstringREQUIRED. Must be the algorithm used to sign the JWT.
HeaderkidstringREQUIRED. Must be DID URI which identifies a particular key in the DID document that the credential shall be bound to.
BodyissstringREQUIRED. MUST be the client_id of the sender.
BodyaudstringREQUIRED. MUST be the client_id of the Credential Issuer.
BodyiatnumberREQUIRED. MUST be the instant when the proof was created
BodyexpnumberREQUIRED. MUST be the instant when the proof will expire.
BodynoncestringREQUIRED. MUST be Token Response c_nonce as provided by the issuer.
Credential Request - Non-normative example
POST into https://api-conformance.ebsi.eu/conformance/v3/issuer-mock/credentials
Content-Type: application/json
Authorization: BEARER eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp..sHQ

{
types: [
'VerifiableCredential',
'VerifiableAttestation',
'VerifiableAuthorisationToOnboard'
],
format: 'jwt_vc',
proof: {
proof_type: 'jwt',
jwt: 'eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIsImFsZyI6IkVTMjU2Iiwia2lkIjoiZGlkOmVic2k6emRQajFHUFhqZkVSWHhYUEUxWVRZZEojN2ozVHBhTmRQTlRPek90b3VPT2tubE9MUWszSlAteWtUZnJhV3RZM0dNRSJ9.eyJpc3MiOiJkaWQ6ZWJzaTp6ZFBqMUdQWGpmRVJYeFhQRTFZVFlkSiIsImF1ZCI6Imh0dHBzOi8vYXBpLWNvbmZvcm1hbmNlLmVic2kuZXUvY29uZm9ybWFuY2UvdjMvaXNzdWVyLW1vY2siLCJpYXQiOjE1ODk2OTk1NjIsImV4cCI6MTU4OTY5OTk2Miwibm9uY2UiOiJQQVBQZjNoOWxleFR2M1dZSFp4OGFqVGUifQ.MbVwusjJsHbhYa-X2opfrahelrJ_ksIK2KKWq8rEiaZEdxIYVN1s4toIKDWoO7j5cJ5AU9HW2cii_-ZWaj3MeA'
}
}

JWT Header:
{
typ: 'openid4vci-proof+jwt',
alg: 'ES256',
kid: 'did:ebsi:zdPj1GPXjfERXxXPE1YTYdJ#7j3TpaNdPNTOzOtouOOknlOLQk3JP-ykTfraWtY3GME'
}
JWT Payload:
{
iss: 'did:ebsi:zdPj1GPXjfERXxXPE1YTYdJ',
aud: 'https://api-conformance.ebsi.eu/conformance/v3/issuer-mock',
iat: 1589699562,
exp: 1589699962,
nonce: 'PAPPf3h9lexTv3WYHZx8ajTe'
}

Credential Response

VCs can be issued either in-time (synchronously) or deferred (asynchronously). Consequently, there are two types of Credential Responses: an in-time type containing format and credential properties, and a deferred type containing an acceptance_token. A c_nonce is always returned for new credential requests.

The Credential Response schema is defined in the following table:

ParameterValueDescription
formatstringCONDITIONAL. The format in which the Verifiable Credential was issued. It MUST be jwt_vc or ldp_vc.
credentialstringCONDITIONAL. Issued Verifiable Credentials in a format indicated by format parameter
acceptance_tokenstringCONDITIONAL. A string containing a token that can be later used to obtain Verifiable Credentials (deferred flow).
c_noncestringOPTIONAL. A string containing a random challenge that needs to be signed to create proof of possession of key material. This value MUST be random and used only once.
c_nonce_expires_inintegerOPTIONAL. Value denoting the lifetime in seconds of the c_nonce.
Credential Response for in-time flow - Non-normative example
{
"format": "jwt_vc",
"credential": "LUpixVCWJk0eOt4CXQe1NXK....WZwmhmn9OQp6YxX0a2L",
"c_nonce": "fGFF7UkhLa",
"c_nonce_expires_in": 86400
}
Credential Response for deferred flow - Non-normative example
{
"acceptance_token": "eyJ0eXAiOiJKV1QiLCJhbGci..zaEhOOXcifQ",
"c_nonce": "wlbQc6pCJp",
"c_nonce_expires_in": 86400
}

Deferred Credential Request

The Deferred Credential Request is used ONLY for the deferred flow. This request uses an acceptance token (from the Credential Response) as the only parameter, which MUST be sent in the HTTP header Authorization as a bearer token.

Deferred Credential Request - Non-normative example
HTTP POST /credential_deferred HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: BEARER 8xLOxBtZp8

Deferred Credential Response

The Deferred Credential Response uses the same schema as the Credential Response see Credential Response. It must contain the format and credential properties.

Security Considerations

All implementations MUST support TLS, and a TLS server certificate MUST be validated.

For more details, see: