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 the 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 provided 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-authorised flow requires a pre-authorised code and PIN, which must be entered 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 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 the 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 a Proof Key for Code Exchange (PKCE) challenge, and the Client is identified (client_id) by their 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 MUST be a signed Request Object.

The Request Object is signed with the Issuer's Authorisation Server's private keys, discoverable through the 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 control of the DID.

The state parameter is mandatory for the ID Token Response when present in the ID Token Request sent by the Authorisation Server. 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 determine 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 to the Credential Issuer's Credential Endpoint.

The client proceeds by requesting issuance of the Verifiable Credential from the Issuer Mock. The requested Credential must match the granted access. The DID document's authentication key must be used for signing the JWT proof, and the DID must match 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. There is no required delay for the deferred flow in the test case; however, the maximum allowed delay is one minute before the test case fails.

Non-normative examples

The requests and responses 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 instead 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 the PIN code and the pre-authorised code used in the Credential Offering through the Conformance UI, which will offer the 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 as those in 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 the 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 as those in 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 passing all tests, the Issuer may apply for a qualification credential. This credential is requested from the Conformance Issuer with the credential type of CTIssueQualificationCredential, which follows the in-time Verifiable Credential Issuance process.