Credential Issuance Guidelines
Context
This document defines Credential Issuance flows in compliance with OIDC 4 Verifiable Credential Issuance (version: openid-connect-4-verifiable-credential-issuance-1_0-05).
Assumptions and boundaries:
- The user has a single User-Agent installed on her device.
- The User-Agent supports the openid:// custom scheme.
- The User-Agent can generate secure random values.
- The User-Agent can store values in session memory.
- openid:// custom scheme is used. The trust-framework-specific scheme is not yet considered.
- Flows describe only the "happy scenarios".
Verifiable Presentation exchange is described in the Verifiable Presentation exchange guidelines
Glossary
Term | Abbreviation | Description |
---|---|---|
Relying Party | RP | A service requiring end-user authentication and claims from a (Self-Issued) OpenID Provider. W3C refers to this as Verifier. |
OpenID Provider | OP | A 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 Provider | SIOP | An OpenID Provider (OP) is used by the End-users to prove control over a cryptographically verifiable identifier. W3C refers to this as a Holder. |
Issuer | A 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. | |
Authentication | AuthN | |
Authorization | AuthZ | |
OpenID Connect | OIDC | OpenID Connect (OIDC) is a simple identity layer on top of the OAuth 2.0 protocol. |
User Agent | SSI wallet, Holder Wallet | |
Verifiable Credential | VC | A set of one or more claims made by an issuer. A verifiable credential is a tamper-evident credential that has authorship that can be cryptographically verified. Verifiable credentials can be used to build verifiable presentations, which can also be cryptographically verified. |
Overview
Below we present a high-level action diagram:
PlantUML diagram code
@startuml credential-issuance-v2
title High-level overview
:Issuance initiation;
:User authentication with issuer;
:User authorised for credentials;
:User consents with credential issuance;
:Create VC and give it to Holder wallet;
@enduml
Issuer Initiated Flow
The user browses her university's home page, searching for a way to obtain a digital diploma. She finds the respective page, which shows a link "request your digital diploma" (issuer initiated, issuer is known in advance). She clicks on this link and is being sent to her digital wallet. The wallet notifies her that an issuer offered to issue a diploma credential. She confirms this inquiry and is being sent to the university's credential issuance service. She logs in with her university login (login with the existing method) and is being asked to consent to the creation of a digital diploma. She confirms and is sent back to her wallet (user consent). There she is notified of the successful creation of the digital diploma (real-time VC issuance).
Steps of the flow can also be seen in a sequence diagram below:
PlantUML diagram code
@startuml credential-issuance-v2
title Issuer initiated credential issuance flow
participant "User" as u
participant "Holder Wallet" as w
participant "Credential Issuer" as ci
autonumber
u -> ci: User clicks a link to issue a VC on issuer's web page
ci -> w: Initiate Issuance Request (QR code or redirect)
w -> u: Notify that issuer offered to issue a VC
u -> w: Consent
w -> ci: Open login page with\nwith Initiate Issuance details (Authorize)
u -> ci: Login to issuer's page
w -> ci: Token Request
ci -> w: Token Response
w -> u: Confirm to continue with Credentials creation
u -> w: Consent Credentials creation
w -> ci: Credential Request
ci -> w: Credential Response (receive VC)
w -> u: VC was successfully created and stored
Issuance initiation
Any issuance initiation scenario mentioned above must support two models of interaction:
Same-Device model: The user has a Holder wallet installed on the same device she uses to visit a credential issuer's website.
Example: Alice has her 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 verifiable credential).
Cross-device model: The user has a Holder wallet on a different device than the one she uses to visit a credential issuer's website.
Example: Alice has her SSI wallet installed on her smartphone, and she visits the credential issuer's website (e.g., the University website where she can request issuance of a diploma verifiable credential) on her laptop. After she clicks on "Issue Credentials", a QR code is presented, and Alice can scan the QR code using her SSI Agent.
In contrast to same-device scenarios, neither the Issuer nor the SSI wallet can communicate with each other via HTTP redirects through an SSI wallet. The protocol flow is therefore modified as follows:
- The Issuer prepares
Initiate Issuance Request
and renders it as a QR code. - The user scans the QR code with her SSI wallet's QR code scanner or smartphone's camera app (in the latter case, the standard mechanisms for invoking the SSI wallet are used on the smartphone - based on the openid:// custom scheme).
- The SSI wallet processes the request.
- Upon completion of the request, the SSI wallet directly sends an HTTP GET request with the response to an endpoint exposed by the Issuer.
QR code method is mainly discussed as a mechanism to initiate a cross-device protocol flow, however, other means to initiate a cross-device flow are possible.
Find more technical details on same-device and cross-device flows here: Verifiable Presentation exchange guidelines in the Verifiable Presentation exchange scenarios section.
User authentication
Users can authenticate via different means
- via existing issuer's authentication service
- by presenting a verifiable credential that is recognised by the issuer and can be used for authentication means
- SIOP v2 authentication is detailed here: Verifiable Presentation exchange guidelines
Details of the issuer-initiated flow
Issuers might want to kick-start the Credentials Issuance process, for this, the wallet must implement the initiate_issuance endpoint. The wallet's endpoint will start the authorisation code flow process if the issuer is trustworthy. The issuance initiation works only as an instruction for the wallet to start the process automatically.
Initiate Issuance Request
Note: As this is a wallet schema/endpoint handler, the wallet doesn't form any response to this and keeps the UX control in the wallet itself. This can also be consumed as a QR code. The wallet can continue the authorisation code flow with the given information to obtain access and later credentials.
Parameter | Value | Description |
---|---|---|
issuer | string | REQUIRED. The issuer URL of the credential issuer, the wallet is requested to obtain one or more credentials from |
credential_type | uri encoded multi-value string | CONDITIONAL. A JSON string denoting the type of credential the wallet shall request. MUST be present if manifest_id is not present |
manifest_id | uri encoded multi-value string | CONDITIONAL. A JSON string referring to a credential manifests published by the credential issuer. MUST be present if credential_type is not present. |
op_state | string | OPTIONAL. String value created by the Credential Issuer and opaque to the wallet that is used to bind the subsequent authentication request with the Credential Issuer to a context set up during previous steps. If the client receives a value for this parameter, it MUST include it in the subsequent Authentication Request to the Credential Issuer as the op_state parameter value. |
pre-authorized_code | string | CONDITIONAL. Only used when Pre-Authorized Code Flow is used. Contains the code, which can be used to get access token from the token endpoint. |
user_pin_required | boolean | CONDITIONAL. Only used when Pre-Authorized Code Flow is used. Instruction to send user_pin into the token endpoint. |
Initiate Issuance Request - Non-normative example
HTTP/1.1 302 Found
Location: openid://initiate_issuance?
issuer=https%3A%2F%2Fserver%2Eexample%2Ecom
&credential_type=https%3A//api-pilot.ebsi.eu/trusted-schemas-registry/v2/schemas/0x1ee207961aba4a8ba018bacf3aaa338df9884d52e993468297e775a585abe4d8
&op_state=eyJhbGciOiJSU0Et...FYUaBy
Pre-Authorised Code Flow
The issuer can also skip the authentication and use pre-authorised flow. This is intended for use cases where the issuer's website ultimately results in one or more credentials to be shared. Pre-authorisation will also use the same Initiate Issuance Request and endpoint but will add the mandatory pre-authorized_code
and optional user_pin_required
parameters into the request. User PIN code must be delivered through other channels, as the QR code or request can be intercepted.
Initiate Issuance Request - Non-normative example
------
INITIATE ISSUANCE REQUEST
------
HTTP/1.1 302 Found
Location: openid://initiate_issuance?
issuer=https%3A%2F%2Fserver%2Eexample%2Ecom
&credential_type=https%3A//api-pilot.ebsi.eu/trusted-schemas-registry/v2/schemas/0x1ee207961aba4a8ba018bacf3aaa338df9884d52e993468297e775a585abe4d8
&pre-authorized_code=SplxlOBeZQQYbYS6WxSbIA
&user_pin_required=true
------
ACCESS TOKEN REQUEST
------
HTTP POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0Mzo=
grant_type=pre-authorized_code
&pre-authorized_code=SplxlOBeZQQYbYS6WxSbIA
&user_pin=94091238
Access with Authorisation Code Flow
Authorisation Code Flow is either started by the user from the wallet or continued by the wallet for the Initiate Issuance Request. The documentation is about the Native client variant of the Authorization Code Flow, which uses PKCE for further security. Native wallet app must have issuer assigned client id for the communications.
Authorisation Request
Authorisation Request builds upon the OAuth 2.0 Rich Authorization Request. The user specifies which types of Verifiable Credentials he/she is requesting using the Authorization Details, which is described in the following table:
Authorization Request
Parameter | Value | Description |
---|---|---|
response_type | string | Value MUST be set to code . |
client_id | string | REQUIRED. The client identifier |
redirect_uri | string | REQUIRED. FQDN for redirection of the response |
scope | string | REQUIRED. Must contain "openid" |
state | string | RECOMMENDED. An opaque value used by the client to maintain state between the request and callback.The authorization server includes this value when redirecting the user-agent back to the client. The parameter SHOULD be used for preventing cross-site request forgery |
authorization_details | uri encodedJSON arrayof objects | REQUIRED. One to many Authorization Details objects below |
request_uri | string | CONDITIONAL: Only used with Push Authentication Request. Other parameters are not used with PAR. |
code_challenge | string | OPTIONAL: In format of BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) code_verifier is client generated secure random, which will be used with token endpoint. |
code_challenge_method | string | OPTIONAL: If the client is capable of using "S256", it MUST use "S256". Else "plain" can only be used if they cannot support "S256". |
Authorisation Details
Parameter | Value | Description |
---|---|---|
type | JSON string | REQUIRED. Determines the authorisation details type. MUST be set to openid_credential for the purpose of this specification |
credential_type | JSON string | CONDITIONAL. Denoting the type of the requested credential. MUST be present if manifest_id is not present |
manifest_id | JSON string | CONDITIONAL. Referring to a credential manifest published by the credential issuer. MUST be present if the type is not present.Manifest can only be used if published by the Server Metadata. |
format | JSON string | OPTIONAL. 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. |
locations | array of JSON strings | OPTIONAL. Allows a client to specify the location of the resource server(s), allowing the AS to mint audience restricted access tokens |
Authorisation Details example
Claim to request a Natural Person Verifiable ID - Non-normative example
[
{
"type": "openid_credential",
"credential_type": "https://api-pilot.ebsi.eu/trusted-schemas-registry/v2/schemas/0x1ee207961aba4a8ba018bacf3aaa338df9884d52e993468297e775a585abe4d8",
"format": "jwt_vc"
}
]
Push Authorisation Request (PAR)
It is recommended to use PAR as an authorisation request. It will overcome the URI limitations and ensures confidentiality, integrity, and authenticity of the request data. Authorisation Request is inside the authorization_details
form field. PAR starts from its own endpoint and will continue into the authorised endpoint.
Push Authorization Request - Non-normative example
PAR REQUEST
----
POST /par HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
response_type=code
&client_id=client_id_for_issuer
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
&authorization_details=%5B%7B%22type%22%3A%22openid_credential...abe4d8%22%2C%22format%22%3A%22jwt_vc%22%7D%5D
&redirect_uri=https%3A%2F%2Fwallet.example.org%2Fcb
--------
PAR RESPONSE
--------
HTTP/1.1 201 Created
Content-Type: application/json
{
"request_uri": "urn:example:bwc4JK-ESC0w8acc191e-Y1LTC2",
"expires_in": 3600
}
-------
AUTHORIZATION
-------
GET /authorize?request_uri=urn:example:bwc4JK-ESC0w8acc191e-Y1LTC2 HTTP/1.1
Host: server.example.com
Plain Authorisation Request
Another option is to use older Authorization Code Flow with authorisation details. Authorisation Request is inside the authorization_details
form field.
Plain Authorization Request - Non-normative example
Redirect or GET
https://server.example.com/authorize?
response_type=code
&client_id=<wallet-specific-client-id>
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
&authorization_details=%5B%7B%22type%22%3A%22openid_credential...abe4d8%22%2C%22format%22%3A%22jwt_vc%22%7D%5D
&redirect_uri=https%3A%2F%2Fwallet.example.org%2Fcb
Authorisation Response
Authorisation Response builds upon the OIDC Authentication Response defined in OIDC Authentication Response.
The authorisation Response schema is defined in the following table:
Parameter | Value | Description |
---|---|---|
redirect_uri | string | REQUIRED. Redirection URI to the wallet. |
code | string | REQUIRED. Query parameter. |
state | string | REQUIRED. Query parameter. |
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
Token Request is defined in the OIDC Token Request. Authorisation code (from Authorisation Response) is exchanged for an Access Token and an ID Token.
Token Request schema is defined in the following table:
Parameter | Value | Description |
---|---|---|
grant_type | string | REQUIRED. MUST be authorisation_code or pre-authorised_code. |
code | string | REQUIRED. MUST be the Authorisation code from the Authorisation Response. |
redirect_uri | string | REQUIRED. Redirection URL to the wallet. |
code_verifier | string | CONDITIONAL: required if client is "public/native mobile" and if authorise endpoint was start with code_challenge .REQUIRED for conformance testing.Wallet generated secure random token, used to validate authorisation original code_challenge . |
pre-authorised_code | string | CONDITIONAL & REQUIRED. Only used when Pre-Authorised Code Flow is used. Code received in the Pre-Authorisation flow. code parameter cannot co-exist in the same request. |
user_pin | string | CONDITIONAL & OPTIONAL. Only used when Pre-Authorised Code Flow is used. Maximum of 8 numbers (0-9). |
Token Request - Non-normative example
HTTP 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
Token Response
Token Response is defined in the OIDC Token Response. In addition to the parameters defined in the OIDC, an additional top-level property c_none MUST be added.
Token Response schema is defined in the following table:
Parameter | Value | Description |
---|---|---|
access_token | string | REQUIRED. The token is later used for the issuance of the actual Verifiable Credential. |
id_token | string | REQUIRED. The token that contains the user's identifier as Subject Identifier. |
token_type | string | REQUIRED. Value of the token type MUST be bearer. |
expires_in | integer | REQUIRED. Value denoting the lifetime in seconds of the token. |
c_nonce | string | REQUIRED. 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_in | integer | OPTIONAL. Value denoting the lifetime in seconds of the c_nonce . |
authorization_pending | boolean | CONDITIONAL & OPTIONAL. Only used when Pre-Authorized Code Flow is used.The token request is still pending as the issuer is waiting for end user end user interaction to complete. The client SHOULD repeat the token request. Before each new request, the client MUST wait at least the number of seconds specified by the interval response parameter. |
interval | integer | CONDITIONAL & OPTIONAL. Only used when Pre-Authorized Code Flow is used.The minimum amount of time in seconds that the client SHOULD wait between polling requests to the token endpoint. If no value is provided, clients MUST use 5 as the default. |
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
}
Requesting Credentials with Access
Credential Request
A credential request is sent to request the issuance of an actual Verifiable Credential. The user can request Verifiable Credentials requested in the Authorisation Request (if defined).
The credential Request schema is defined in the following table:
Parameter | Value | Description |
---|---|---|
type | string | REQUIRED. URI to the schema of requested Verifiable Credential. The URI MUST point to a schema in the Trusted Schema Registry. |
format | string | OPTIONAL. The format in which the Verifiable Credential should be issued. It MUST be jwt_vc or ldp_vc. |
proof | Proof object | OPTIONAL. An object containing proof of possession of the key material. The proof is generated based on the c_nonce from Token Response. |
Proof
Parameter | Value | Description |
---|---|---|
proof_type | string | REQUIRED. JSON String denoting the proof type. It MUST be "jwt" |
jwt | Signed JWT | CONDITIONAL, when proof_type is jwt. Objects of this type contain a single jwt element with a signed JWT as proof of possession |
Signed JWT proof must contain the following parameters:
Location | Parameter | Value | Description |
---|---|---|---|
Header | kid | string | CONDITIONAL. JWT header containing the key ID. If the credential shall be bound to a DID, the kid refers to a DID URL which identifies a particular key in the DID document that the credential shall be bound to. |
Header | jwk | JWK object | CONDITIONAL. JWT header containing the key material the new credential shall be bound to. MUST NOT be present if kid is present.REQUIRED for EBSI DID Method for Natural Persons. |
Body | iss | string | REQUIRED. MUST contain the client_id of the sender. |
Body | aud | string | REQUIRED. MUST contain the issuer URL of the credential issuer. |
Body | iat | number | REQUIRED. MUST contain the instant when the proof was created. |
Body | nonce | string | REQUIRED. MUST be Token Response c_nonce as provided by the issuer. |
Credential Request data - Non-normative example
{
"type": "https://api-pilot.ebsi.eu/trusted-schemas-registry/v2/schemas/0x1ee207961aba4a8ba018bacf3aaa338df9884d52e993468297e775a585abe4d8",
"format": "jwt_vc",
"proof": {
"proof_type": "jwt",
"jwt": "eyJraWQiOiJkaWQ6ZXhhbXBsZTplYmZlYjFmNzEyZWJjNmYxYzI3NmUxMmVjMjEva2V5cy8
xIiwiYWxnIjoiRVMyNTYiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJzNkJoZFJrcXQzIiwiYXVkIjoiaHR
0cHM6Ly9zZXJ2ZXIuZXhhbXBsZS5jb20iLCJpYXQiOiIyMDE4LTA5LTE0VDIxOjE5OjEwWiIsIm5vbm
NlIjoidFppZ25zbkZicCJ9.ewdkIkPV50iOeBUqMXCC_aZKPxgihac0aW9EkL1nOzM"
}
}
Decoded proof JWT - Non-normative example
{
"alg": "ES256",
"typ": "JWT",
"kid":"did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1"
}.
{
"iss": "s6BhdRkqt3",
"aud": "https://server.example.com",
"iat": "2018-09-14T21:19:10Z",
"nonce": "tZignsnFbp"
}
Credential Request - Non-normative example
HTTP POST /credential HTTP/1.1
Host: server.example.com
Content-Type: application/json
Authorization: BEARER <ACCESS TOKEN>
{
"type": "https://api-pilot.ebsi.eu/trusted-schemas-registry/v2/schemas/0x1ee207961aba4a8ba018bacf3aaa338df9884d52e993468297e775a585abe4d8",
"format": "jwt_vc",
"proof": {
"proof_type": "jwt",
"jwt": "eyJraWQiOiJkaWQ6ZXhhbXBsZTplYmZlYjFmNzEyZWJjNmYxYzI3NmUxMmVjMjEva2V5cy8
xIiwiYWxnIjoiRVMyNTYiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJzNkJoZFJrcXQzIiwiYXVkIjoiaHR
0cHM6Ly9zZXJ2ZXIuZXhhbXBsZS5jb20iLCJpYXQiOiIyMDE4LTA5LTE0VDIxOjE5OjEwWiIsIm5vbm
NlIjoidFppZ25zbkZicCJ9.ewdkIkPV50iOeBUqMXCC_aZKPxgihac0aW9EkL1nOzM"
}
}
Credential Response
As described in the first section of this page, Verifiable Credentials can be issued in two ways: immediately (or synchronous) or deferred. Therefore, there are two types of Credential Response: synchronous containing format and credential properties; deferred containing acceptance_token
.
The credential Response schema is defined in the following table:
Parameter | Value | Description |
---|---|---|
format | string | CONDITIONAL. The format in which the Verifiable Credential was issued. It MUST be jwt_vc or ldp_vc. |
credential | string | CONDITIONAL. Issued Verifiable Credentials in a format indicated by format parameter |
acceptance_token | string | CONDITIONAL. A string containing a token that can be later used to obtain Verifiable Credentials (deferred flow). |
c_nonce | string | OPTIONAL. 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_in | integer | OPTIONAL. Value denoting the lifetime in seconds of the c_nonce . |
Credential Response in synchronous flow - Non-normative example
{
"format": "jwt_vc",
"credential": "LUpixVCWJk0eOt4CXQe1NXK....WZwmhmn9OQp6YxX0a2L",
"c_nonce": "fGFF7UkhLa",
"c_nonce_expires_in": 86400
}
Credential Response in deferred flow - Non-normative example
{
"acceptance_token": "8xLOxBtZp8",
"c_nonce": "wlbQc6pCJp",
"c_nonce_expires_in": 86400
}
Deferred Credential Request
Deferred Credential Request is used ONLY in the deferred flow. This request uses an acceptance token (from Credential Response) as the only parameter, which MUST be sent as an access token in the HTTP header.
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
Deferred Credential Response uses the same schema as Credential Response (see Credential Response). Properties format and credentials MUST be contained.
Security Considerations
All implementations MUST support TLS, and a TLS server certificate MUST be performed.
References
- OpenID Connect Core specification: https://openid.net/specs/openid-connect-core-1_0.html
- OpenID Connect 4 Verifiable Credential Issuance: https://openid.net/specs/openid-connect-4-verifiable-credential-issuance-1_0.html
- OpenID Connect 4 Verifiable Credential Issuance (latest): https://bitbucket.org/openid/connect/src/master/openid-connect-4-verifiable-credential-issuance/openid-connect-4-verifiable-credential-issuance-1_0.md
- OpenID Connect Self-Issued OpenID Provider v2: https://openid.net/specs/openid-connect-self-issued-v2-1_0.html
- OpenID Connect 4 Verifiable Presentations: https://openid.net/specs/openid-connect-4-verifiable-presentations-1_0.html
- SSI Presentation: https://openid.net/wordpress-content/uploads/2021/09/OIDF_OIDC4SSI-Update_Kristina-Yasuda-Torsten-Lodderstedt.pdf
- JSON Web Token (JWT): https://datatracker.ietf.org/doc/html/rfc7519
- OAuth 2.0 Threat Model and Security Considerations: https://datatracker.ietf.org/doc/html/rfc6819
- Introduction to QR Code
- QR Code Size Setting Instruction
- Online QR Code generator
- https://www.qrcode.com/en/howto/cell.html
- Verifiable Presentation exchange guidelines