Skip to main content
European CommissionEBSI European Blockchain

Issue Verifiable Credentials

Last updated on

Context

This document describes the Verifiable Credential Issuance test module flows from Conformance Test.

The module tests the Verifiable Credential Issuance flows without EBSI as a trust anchor. The Credential Issuer DID and Holder Wallet DID are based on did:key; the trust framework is not defined, and the Credential Offering is given by the Conformance UI instead of the Credential Issuer (test subject).

Guidelines

All tests start with a Credential Offering initiated by the Conformance UI on behalf of the Credential Issuer. The pre-authorized flow requires a pre-authorized code and pin, which must be inserted through the UI. The Credential issuer may have split responsibilities for issuance and authentication, or both functionalities may be included in the same service. After the discovery process is complete, the test case will proceed.

Discovery

Please see Wallets Metadata specification for further details.

Credential Offering

Please see VCI - Credential Offering for further details.

Tests

In-time Issuance

The user initiates the Credential Offering through the Conformance UI, which will offer CTWalletSameAuthorisedInTime to Holder Wallet on behalf of the Credential Issuer. The Credential Issuer must request an ID Token and issue CTWalletSameAuthorisedInTime synchronously.

Non-normative examples

The client proceeds with the Verifiable Credential Issuance flow by requesting access for the required credential from the Authorisation Server. The instructions for the request are contained in the Credential Offering.

The Authorisation Request is plain, contains PKCE challenge, and the Client is identified (client_id) with DID.

Authorisation Request
GET from https://my-issuer.rocks/auth/authorize?
response_type=code
&scope=openid
&issuer_state=tracker%3Dvcfghhj
&state=client-state
&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%22CTWalletSameAuthorisedInTime%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%2F%2F%22%7D

The Issuer's Authorisation Server validates the request and proceeds by requesting authentication of a DID from the client. The ID Token Request also serves as an Authorisation Request, and it MUST be a signed Request Object.

The Request Object is signed with the Issuer's Authorisation Server's private keys, which are discoverable through jwks_uri parameter in ./well-known/openid-credential-issuer. The request must use response_mode=direct_post, and the response location is delivered in the redirect_uri. The redirect location is defined by the Authorisation Request's client_metadata.authorization_endpoint, or defaults to openid: if missing.

ID Token Request
HTTP 302 Location: openid://
client_id=https%3A%2F%2Fmy-issuer.rocks%2Fauth
&response_type=id_token
&scope=openid
&redirect_uri=https%3A%2F%2Fmy-issuer.rocks%2Fauth%2Fdirect_post
&request=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImM0S3JlcEpYem1CTVctcW8ybnREQ3drVGdMbTJDYl81ZWFiemtsalRoXzAifQ.eyJpc3MiOiJodHRwczovL215LWlzc3Vlci5yb2Nrcy9hdXRoIiwiYXVkIjoiZGlkOmtleTp6MmRtekQ4MWNnUHg4VmtpN0pidXVNbUZZcldQZ1lveXR5a1VaM2V5cWh0MWo5S2JzRVl2ZHJqeE1qUTR0cG5qZTlCREJUenVORFAza25uNnFMWkVyemQ0Yko1Z28yQ0Nob1BqZDVHQUgzenBGSlA1ZnV3U2s2NlU1UHE2RWhGNG5Lbkh6RG56bkVQOGZYOTluWkdnd2JBaDFvN0dqMVg1MlRkaGY3VTRLVGs2NnhzQTVyIiwiZXhwIjoxNTg5Njk5MTYyLCJyZXNwb25zZV90eXBlIjoiaWRfdG9rZW4iLCJyZXNwb25zZV9tb2RlIjoiZGlyZWN0X3Bvc3QiLCJjbGllbnRfaWQiOiJodHRwczovL215LWlzc3Vlci5yb2Nrcy9hdXRoIiwicmVkaXJlY3RfdXJpIjoiaHR0cHM6Ly9teS1pc3N1ZXIucm9ja3MvYXV0aC9kaXJlY3RfcG9zdCIsInNjb3BlIjoib3BlbmlkIiwibm9uY2UiOiJuLTBTNl9XekEyTWoifQ.Vg615ydUGWQBM_o0mSoBePYTPyplbmcFv1oWa2mF3K-CeB9n6biCqmP-1w2jBLxSHVbIJlz_Ta0hc9pFWsewRQ

JWT Header:
{
typ: 'JWT',
alg: 'ES256',
kid: 'c4KrepJXzmBMW-qo2ntDCwkTgLm2Cb_5eabzkljTh_0'
}
JWT Payload:
{
iss: 'https://my-issuer.rocks/auth',
aud: 'did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r',
exp: 1589699162,
response_type: 'id_token',
response_mode: 'direct_post',
client_id: 'https://my-issuer.rocks/auth',
redirect_uri: 'https://my-issuer.rocks/auth/direct_post',
scope: 'openid',
nonce: 'n-0S6_WzA2Mj'
}

The client proceeds by issuing an ID Token signed by the DID document's authentication key. This will be used to prove the control of the DID.

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

ID Token Response
POST into https://my-issuer.rocks/auth/direct_post
Content-Type: application/x-www-form-urlencoded
id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDprZXk6ejJkbXpEODFjZ1B4OFZraTdKYnV1TW1GWXJXUGdZb3l0eWtVWjNleXFodDFqOUtic0VZdmRyanhNalE0dHBuamU5QkRCVHp1TkRQM2tubjZxTFpFcnpkNGJKNWdvMkNDaG9QamQ1R0FIM3pwRkpQNWZ1d1NrNjZVNVBxNkVoRjRuS25IekRuem5FUDhmWDk5blpHZ3diQWgxbzdHajFYNTJUZGhmN1U0S1RrNjZ4c0E1ciJ9.eyJpc3MiOiJkaWQ6a2V5OnoyZG16RDgxY2dQeDhWa2k3SmJ1dU1tRllyV1BnWW95dHlrVVozZXlxaHQxajlLYnNFWXZkcmp4TWpRNHRwbmplOUJEQlR6dU5EUDNrbm42cUxaRXJ6ZDRiSjVnbzJDQ2hvUGpkNUdBSDN6cEZKUDVmdXdTazY2VTVQcTZFaEY0bktuSHpEbnpuRVA4Zlg5OW5aR2d3YkFoMW83R2oxWDUyVGRoZjdVNEtUazY2eHNBNXIiLCJzdWIiOiJkaWQ6a2V5OnoyZG16RDgxY2dQeDhWa2k3SmJ1dU1tRllyV1BnWW95dHlrVVozZXlxaHQxajlLYnNFWXZkcmp4TWpRNHRwbmplOUJEQlR6dU5EUDNrbm42cUxaRXJ6ZDRiSjVnbzJDQ2hvUGpkNUdBSDN6cEZKUDVmdXdTazY2VTVQcTZFaEY0bktuSHpEbnpuRVA4Zlg5OW5aR2d3YkFoMW83R2oxWDUyVGRoZjdVNEtUazY2eHNBNXIiLCJhdWQiOiJodHRwczovL215LWlzc3Vlci5yb2Nrcy9hdXRoIiwiZXhwIjoxNTg5Njk5MzYwLCJpYXQiOjE1ODk2OTkyNjAsIm5vbmNlIjoibi0wUzZfV3pBMk1qIn0.7EfM_Ne0Oa-pAMSehOxRDygP_zASUgw31Q7oLBsAXKAFNPtN9-Xwfl0LoB0BbjQAYPGOjQJbGlz0pCwAqBX-7Q

JWT Header:
{
typ: 'JWT',
alg: 'ES256',
kid: 'did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r'
}
JWT Payload:
{
iss: 'did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r',
sub: 'did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r',
aud: 'https://my-issuer.rocks/auth',
exp: 1589699360,
iat: 1589699260,
nonce: 'n-0S6_WzA2Mj'
}

The Authorisation Server evaluates the authentication response and original authorisation request to assert if access should be granted. Upon successful authentication, the direct_post endpoint returns a redirect to the originally requested redirect_uri with a code.

Authorisation Response
HTTP/1.1 302 Found
Location: openid://?
code=SplxlOBeZQQYbYS6WxSbIA
&state=client-state

The client proceeds with the code flow by calling the Token Endpoint with the required details and providing a code_verifier corresponding to the initial Authorisation Request code_challenge.

Token Request
POST into https://my-issuer.rocks/auth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&client_id=did%3Akey%3Az2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r
&code=SplxlOBeZQQYbYS6WxSbIA
&code_verifier=random-secret

The Access Token is delivered as a response payload from a successful Token Endpoint initiation. The c_nonce (Challenge Nonce) must be stored by the client until a new one is provided.

Token Response
HTTP Response Payload: Content-Type application/json
{
access_token: 'eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp..sHQ',
token_type: 'bearer',
expires_in: 86400,
id_token: 'eyJodHRwOi8vbWF0dHIvdGVuYW50L..3Mz',
c_nonce: 'PAPPf3h9lexTv3WYHZx8ajTe',
c_nonce_expires_in: 86400
}

At this point, the client has successfully obtained a valid access_token, which can be used to gain access into the Credential Issuer's Credential Endpoint.

The client proceeds by requesting issuance of the Verifiable Credential from the Issuer Mock. The requested Credential must be equal to the granted access. The DID document's authentication key must be used for signing the JWT proof, and the DID must equal the same as the one used in authentication.

Credential Request
POST into https://my-issuer.rocks/auth/credentials
Content-Type: application/json
Authorization: BEARER eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp..sHQ

{
types: [
'VerifiableCredential',
'VerifiableAttestation',
'CTWalletSameAuthorisedInTime'
],
format: 'jwt_vc',
proof: {
proof_type: 'jwt',
jwt: 'eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIsImFsZyI6IkVTMjU2Iiwia2lkIjoiZGlkOmtleTp6MmRtekQ4MWNnUHg4VmtpN0pidXVNbUZZcldQZ1lveXR5a1VaM2V5cWh0MWo5S2JzRVl2ZHJqeE1qUTR0cG5qZTlCREJUenVORFAza25uNnFMWkVyemQ0Yko1Z28yQ0Nob1BqZDVHQUgzenBGSlA1ZnV3U2s2NlU1UHE2RWhGNG5Lbkh6RG56bkVQOGZYOTluWkdnd2JBaDFvN0dqMVg1MlRkaGY3VTRLVGs2NnhzQTVyI3oyZG16RDgxY2dQeDhWa2k3SmJ1dU1tRllyV1BnWW95dHlrVVozZXlxaHQxajlLYnNFWXZkcmp4TWpRNHRwbmplOUJEQlR6dU5EUDNrbm42cUxaRXJ6ZDRiSjVnbzJDQ2hvUGpkNUdBSDN6cEZKUDVmdXdTazY2VTVQcTZFaEY0bktuSHpEbnpuRVA4Zlg5OW5aR2d3YkFoMW83R2oxWDUyVGRoZjdVNEtUazY2eHNBNXIifQ.eyJpc3MiOiJkaWQ6a2V5OnoyZG16RDgxY2dQeDhWa2k3SmJ1dU1tRllyV1BnWW95dHlrVVozZXlxaHQxajlLYnNFWXZkcmp4TWpRNHRwbmplOUJEQlR6dU5EUDNrbm42cUxaRXJ6ZDRiSjVnbzJDQ2hvUGpkNUdBSDN6cEZKUDVmdXdTazY2VTVQcTZFaEY0bktuSHpEbnpuRVA4Zlg5OW5aR2d3YkFoMW83R2oxWDUyVGRoZjdVNEtUazY2eHNBNXIiLCJhdWQiOiJodHRwczovL215LWlzc3Vlci5yb2Nrcy9hdXRoIiwiaWF0IjoxNTg5Njk5NTYyLCJleHAiOjE1ODk2OTk5NjIsIm5vbmNlIjoiUEFQUGYzaDlsZXhUdjNXWUhaeDhhalRlIn0.Oa2Mk135r_bxTrJWFOVRwOXiQrE2Vm3bVilmBCLnQhIdAFJ30qFb4d31yCC9ZNCmgzhYGzp39wqJBt5z3jMJ0Q'
}
}

JWT Header:
{
typ: 'openid4vci-proof+jwt',
alg: 'ES256',
kid: 'did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r'
}
JWT Payload:
{
iss: 'did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r',
aud: 'https://my-issuer.rocks/auth',
iat: 1589699562,
exp: 1589699962,
nonce: 'PAPPf3h9lexTv3WYHZx8ajTe'
}

After the successful request, the response payload will contain the requested credential.

Credential Response
HTTP Response Payload: Content-Type application/json
{
format: 'jwt_vc',
credential: 'LUpixVCWJk0eOt4CXQe1NXK....WZwmhmn9OQp6YxX0a2L',
c_nonce: 'fGFF7UkhLa',
c_nonce_expires_in: 86400
}

Deferred Issuance

The user initiates the Credential Offering through the Conformance UI, which offers CTWalletSameAuthorisedDeferred to the Holder Wallet on behalf of the Credential Issuer. The Credential Issuer must request an ID Token and defer the CTWalletSameAuthorisedDeferred issuance. For the test case, there is no need to have any delay for the deferred flow; however, the maximum allowed delay is one minute before the test case will fail.

Non-normative examples

The requests and responses used are semantically the same as those in the in-time flow. The last Credential Response is also semantically identical to the in-time Credential Response.

In the deferred flow, a Deferred Credential Response is returned in place of a Credential Response. The deferred response contains an "acceptance_token" to be used in the deferred endpoint.

Credential Response with acceptance_token
HTTP Response Payload: Content-Type application/json
{
acceptance_token: 'eyJ0eXAiOiJKV1QiLCJhbGci..zaEhOOXcifQ',
c_nonce: 'fGFF7UkhLa',
c_nonce_expires_in: 86400
}
Deferred credential request
POST https://my-issuer.rocks/auth/credential_deferred
Authorization: BEARER eyJ0eXAiOiJKV1QiLCJhbGci..zaEhOOXcifQ

Pre-authorised Issuance

In time

The user must input pin-code and the pre-authorised code used in the Credential Offering through the Conformance UI, which will offer CTWalletSamePreAuthorisedInTime to the Holder Wallet on behalf of the Credential Issuer. The Credential Issuer must issue CTWalletSamePreAuthorisedInTime synchronously.

Non-normative examples

All other payloads are semantically the same with the In-time flow.

Token Request
POST into https://my-issuer.rocks/auth/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code
&user_pin=1234
&pre-authorized_code=SplxlOBeZQQYbYS6WxSbIA

Deferred

The user must input pin-code and the pre-authorised code used in the Credential Offering through the Conformance UI, which will offer CTWalletSamePreAuthorisedDeferred to the Holder Wallet on behalf of the Credential Issuer. The Credential Issuer must issue CTWalletSamePreAuthorisedDeferred.

Non-normative examples

All other payloads are semantically the same with the deferred flow.

Token Request
POST into https://my-issuer.rocks/auth/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code
&user_pin=1234
&pre-authorized_code=SplxlOBeZQQYbYS6WxSbIA

Applying for Qualification

After all tests has been passed, the Issuer may apply for a qualification credential. The credential is requested from the Conformance Issuer with the credential type of CTIssueQualificationCredential, which follows the in-time Verifiable Credential Issuance process.