Receive and Store Digital Credentials (W3C & SD-JWT VC) Via OID4VCI

There are several methods by which a user can receive a credential into their wallet, and all are supported by the wallet API:

  1. User may receive a direct link, which opens the wallet and prompts credential acceptance.
  2. A website may display a QR code for the user to scan and accept the credential.
  3. The user may manually input an offer URL string into a text field.

Irrespective of the chosen method, these are just different ways of receiving, parsing and fulfilling a request URL.

Credential Offer URL

A credential offer URL is a standardized method, as per the OID4VCI specification, to communicate the issuance of credentials between issuer and wallet. This URL can take various forms, such as a QR code or a link, and generally begins with openid-credential-offer://.

Example Offer URL

openid-credential-offer://issuer.portal.walt.id/?credential_offer=<credential_offer>

Within the URL, a query parameter credential_offer provides the actual offer as a JSON object.

Example of a Credential Offer Object

{
  "credential_issuer": "https://issuer.portal.walt.id",
  // This is the URL of the issuer of the credential(s)
  "credentials": [
    // This array contains details about the types of credentials being offered
    {
      "format": "jwt_vc_json",
      // Specifies the format of the credential
      "types": [
        // An array indicating the credential types
        "VerifiableCredential",
        "BankId"
      ],
      "credential_definition": {
        "@context": [
          // Provides the contexts of the credential
          "https://www.w3.org/2018/credentials/v1"
        ],
        "types": [
          // Reiterates the type of credential being offered.
          "VerifiableCredential",
          "BankId"
        ]
      }
    }
  ],
  "grants": {
    // Specifies how the credentials can be obtained
    "authorization_code": {
      "issuer_state": "<issuer_state>"
      // Contains an issuer_state, a unique identifier for the grant
    },
    "urn:ietf:params:oauth:grant-type:pre-authorized_code": {
      // Provides details for a pre-authorized code grant type,
      //including the actual pre-authorized_code and a flag user_pin_required indicating whether a PIN is required
      // for user authentication.
      "pre-authorized_code": "<pre-authorized_code>",
      "user_pin_required": false
    }
  }
}

Accepting Credential Offers

For a user's wallet to accept the credential via the credential offer URL, the URL must be parsed and the offer fulfilled. The Wallet API manages this process once provided the offer URL. More sophisticated scenarios include presenting the credentials to the user before accepting the credential offer. See an example below.

Accepting the offer

Accept the offer by making a POST call to the /wallet-api/wallet/{wallet}/exchange/useOfferRequest endpoint. This requires passing a user's did and the walletId in the parameters and the offer URL in the body. The API will parse the request, receive the credentials from the issuer, and store them in the user's wallet.

Example Call | Api Reference

curl -X 'POST' \
  'http://0.0.0.0:7001/wallet-api/wallet/{wallet}/exchange/useOfferRequest?did=did%3Akey%3Az6MknMppUdsg34t6oPevGDijgB9w9862Ai6Xu5iexjNU2Uoh' \
  -H 'accept: */*' \
  -H 'Content-Type: text/plain' \
  -d 'openid-credential-offer://issuer.portal.walt.id/?credential_offer=<credential_offer>'

You can receive the needed wallet id parameter via the /wallet-api/wallet/accounts/wallets endpoint.

Example Display Of Credentials To User

In JavaScript, assuming that you already have the offer URL and a function decodeOfferURL(offerURL) that returns the JSON offer object, you might do it like this:

// Assuming we have the offer URL
var offerURL = "openid-credential-offer://...";

// Extract Offer object from the URL
var offerObject = decodeOfferURL(offerURL);

// Iterate through credentials within the offer object and present to the user
var credentialDetails = offerObject["credentials"].map((credential) => {
  var lastType = credential.types[credential.types.length - 1]; // Get last type
  return {
    format: credential.format,
    type: lastType,
  };
});

// Display credential details
credentialDetails.forEach((credential) => {
  console.log(`Format: ${credential.format}`);
  console.log(`Type: ${credential.type}`);
});

The user would then see a list of credentials that would be received, and they could confirm whether they want to proceed with accepting the offer.

Note that the above code is a simple example and assumes that decodeOfferURL(offerURL) exists. In a real application, you would likely use a library to perform URL parsing and JSON decoding, and handle any errors appropriately.