Step by step

Let's take a look at each of the steps described in the Lea use case.

Before delving into the details, we suggest you get a good overview of the process as a whole. So, before unfolding each of the sub-items of each step of the use case, try to understand what role that specific item plays within the overall flow.

1. Imagine a scenario where a person - Lea - visits her government's website.

So, Lea accessed something like http://agency.gov

2. She logs into her account and is offered an ID wallet as well as digital versions of her passport, driver’s license, proof of residence and social security card.

After logging in, the government website offers Lea two things (unfold the itens below for more):

  • a wallet app to be installed in her mobile device

    It could even be a web wallet but it's not very common. A mobile wallet app fits in better with the whole decentralized identity philosophy.

  • a set of official credentials to be stored in her Wallet

    This is where the magic starts to happen.

    Digital verifiable credentials are not compulsorily pushed into the user's wallet. They are first offered by the issuing authority and it is up to the user to accept them or not in their wallet.

    So, the very first step is for the Issuer to generate the credential offer. The offer usually comes in the form of a long URL that is used by the Wallet to tell the Issuer if it accepts the offered credentials or not.

    As the offer URL is generated on the Issuer side (i.e. on the government website) and needs to be read by the wallet, the most convenient way to bring it to the Wallet is by generating a QrCode that represents this URL. The wallet reads the QrCode, converts it back to the offer URL and then calls it to inform the Issuer that the credentials can be issued because the user has accepted them.

    In Walt.id, the credential offer URL is generated via the following HTTP call to the /openid4vc/jwt/issue endpoint:

    curl -X 'POST' \
    'https://issuer.portal.walt.id/openid4vc/jwt/issue' \
    -H 'accept: text/plain' \
    -H 'Content-Type: application/json' \
    -d '{
      "issuerKey": {
        "type": "jwk",
        "jwk": {
          "kty": "OKP",
          "d": "mDhpwaH6JYSrD2Bq7Cs-pzmsjlLj4EOhxyI-9DM1mFI",
          "crv": "Ed25519",
          "kid": "Vzx7l5fh56F3Pf9aR3DECU5BwfrY6ZJe05aiWYWzan8",
          "x": "T3T4-u1Xz3vAV2JwPNxWfs4pik_JLiArz_WTCvrCFUM"
        }
      },
      "issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
      "credentialConfigurationId": "OpenBadgeCredential_jwt_vc_json",
      "credentialData": {
        "@context": [
          "https://www.w3.org/2018/credentials/v1",
          "https://purl.imsglobal.org/spec/ob/v3p0/context.json"
        ],
        "id": "urn:uuid:THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION (see below)",
        "type": [
          "VerifiableCredential",
          "OpenBadgeCredential"
        ],
        "name": "JFF x vc-edu PlugFest 3 Interoperability",
        "issuer": {
          "type": [
            "Profile"
          ],
          "id": "did:key:THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION FROM CONTEXT (see below)",
          "name": "Jobs for the Future (JFF)",
          "url": "https://www.jff.org/",
          "image": "https://w3c-ccg.github.io/vc-ed/plugfest-1-2022/images/JFF_LogoLockup.png"
        },
        "issuanceDate": "2023-07-20T07:05:44Z (THIS WILL BE REPLACED BY DYNAMIC DATA FUNCTION (see below))",
        "expirationDate": "WILL BE MAPPED BY DYNAMIC DATA FUNCTION (see below)",
        "credentialSubject": {
          "id": "did:key:123 (THIS WILL BE REPLACED BY DYNAMIC DATA FUNCTION (see below))",
          "type": [
            "AchievementSubject"
          ],
          "achievement": {
            "id": "urn:uuid:ac254bd5-8fad-4bb1-9d29-efd938536926",
            "type": [
              "Achievement"
            ],
            "name": "JFF x vc-edu PlugFest 3 Interoperability",
            "description": "This wallet supports the use of W3C Verifiable Credentials and has demonstrated interoperability during the presentation request workflow during JFF x VC-EDU PlugFest 3.",
            "criteria": {
              "type": "Criteria",
              "narrative": "Wallet solutions providers earned this badge by demonstrating interoperability during the presentation request workflow. This includes successfully receiving a presentation request, allowing the holder to select at least two types of verifiable credentials to create a verifiable presentation, returning the presentation to the requestor, and passing verification of the presentation and the included credentials."
            },
            "image": {
              "id": "https://w3c-ccg.github.io/vc-ed/plugfest-3-2023/images/JFF-VC-EDU-PLUGFEST3-badge-image.png",
              "type": "Image"
            }
          }
        }
      },
      "mapping": {
        "id": "<uuid>",
        "issuer": {
          "id": "<issuerDid>"
        },
        "credentialSubject": {
          "id": "<subjectDid>"
        },
        "issuanceDate": "<timestamp>",
        "expirationDate": "<timestamp-in:365d>"
      }
    }'
    

    For the moment, let's not focus on the details. This is what is important to know:
    • we called an endpoint to generate a credential offer URL
    • the request body is a JSON data structure
    • this JSON is composed by two big set of attributes
    • some of them are related to the credential to be offered (issuerKey, issuerDid, credentialConfigurationId, credentialData)
    • the last one (mapping) is a map to assign dynamic data to the credentialData field by using data functions

    If you want to know more about what happened here, take a look at the full documentation.

    The resul of this call is the credential offer URL. Something like this:

    openid-credential-offer://issuer.portal.walt.id/?credential_offer_uri=https%3A%2F%2Fissuer.portal.walt.id%2Fopenid4vc%2FcredentialOffer%3Fid%3Da8d7fe64-eb3f-4729-a5d3-9685339167f7
    

    Save it for later.

3. She opens (or downloads) an ID wallet and requests the credentials.

Now, after installing the wallet app in her mobile device, Lea:

  • opens the wallet app
  • logs in to the Wallet app with her credential access

    Do it calling the /wallet-api/auth/login endpoint.

    curl -X 'POST' \
      'https://wallet.walt.id/wallet-api/auth/login' \
      -H 'accept: */*' \
      -H 'Content-Type: application/json' \
      -d '{
      "type": "email",
      "email": "user@email.com",
      "password": "password"
    }'
    
  • selects which wallet she wants to use

    Let’s get it straight.

    • Lea has a Wallet App installed on her mobile device.
    • For security reasons, the Wallet App requires an authenticated user to be used.
    • Each user can have multiple wallets associated with their account. It's like the various slots of your pocket wallet. You can use them as you wish. For example, one for credit cards, another one for identity documents, etc. It's up to you.
    • So, before accepting the offered credential, Lea needs to select in which wallet she wants to store it.

    The list of available wallets under the logged-in account can be gotten by calling the /wallet-api/wallet/accounts/wallets endpoint.

    curl -X 'GET' \
      'https://wallet.walt.id/wallet-api/wallet/accounts/wallets' \
      -H 'accept: application/json'
    

    The response will be something like this:

    {
      "account": "0bce848c-aa7c-4dec-bd87-535f120c8fbb",
      "wallets": [
        {
          "id": "6e802709-7d60-4dd2-a599-6c775c8a66eb",
          "name": "Wallet of user@email.com",
          "createdOn": "2024-05-23T10:37:14.380Z",
          "addedOn": "2024-05-23T10:37:14.387Z",
          "permission": "ADMINISTRATE"
        }
      ]
    }
    

    In this example, there's only one wallet available for the authenticated user@email.com. Locate and note the id of the only wallet available. It will be needed for the next calls.

  • makes sure she has a DID to be used

    The core of the Decentralized Identity described in the playbook is the Decentralized Identifiers (DIDs). As such, every entity involved in this process of issuing, managing and verifying credentials needs to be identified through DIDs.

    There are several ways to create DIDs, using several strategies and technologies. We are not going to focus on that right know. If want to dive into it, take a look at here.

    The Issuer DID was hardcoded in the URL offer generation request presented above.

    As the aim of this guide is to present the simplest possible flow for issuing and verifying credentials, we could also hardcode the DID of the subject to whom the credential refers. However, as this is just 1 simple endpoint call, let's take the opportunity to show a little more of the potential of our Wallet API.

    Let's create a DID for Lea:

    curl -X 'POST' \
    'https://wallet.walt.id/wallet-api/wallet/6e802709-7d60-4dd2-a599-6c775c8a66eb/dids/create/key' \
    -H 'accept: */*' \
    -d ''
    

    Note that we used the wallet id in the URL of the HTTP call.

    This is the generated DID:

    did:key:z6MkoFpAKhfFrZRrBERK8nMNGuRSdhDUrymbF41AsKM8m8TJ
    

    Save it for later.

  • scans the QrCode presented by the government's website

    This is the moment when the Wallet has access to the offer URL of the previously generated credential.

    The QrCode is scanned by the Wallet App and decoded back to the offer URL.

  • accepts the offered credential

    This is a very specific Wallet App behaviour. It's when the App asks the user if she accepts or not the offered credential. If she does, we jump to the next step. If not, do nothing.

4. The government issues the credentials to Lea.

With the offer URL in hands, we will now call the /wallet-api/wallet/{wallet}/exchange/useOfferRequest endpoint to signal the Issuer that the offered credential can be issued.

  • ask the Issuer to issue the offered credentials

    To make this request, we will need 3 piece of data retrieved from previous calls:

    • the wallet id -> in the URL
    • the subject (Lea) DID -> in the URL
    • the offer URL -> in the request body
    curl -X 'POST' \
      'https://wallet.walt.id/wallet-api/wallet/6e802709-7d60-4dd2-a599-6c775c8a66eb/exchange/useOfferRequest?did=did%3Akey%3Az6MkoFpAKhfFrZRrBERK8nMNGuRSdhDUrymbF41AsKM8m8TJ' \
      -H 'accept: application/json' \
      -H 'Content-Type: text/plain' \
      -d 'openid-credential-offer://issuer.portal.walt.id/?credential_offer_uri=https%3A%2F%2Fissuer.portal.walt.id%2Fopenid4vc%2FcredentialOffer%3Fid%3D9b370113-0b27-465d-bd05-175240c7730a'
    

    And this is how the result will look like:

    [
      {
        "wallet": "6e802709-7d60-4dd2-a599-6c775c8a66eb",
        "id": "urn:uuid:b9843e3a-97ab-4c6f-ae43-01e2c495ff69",
        "document": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImRpZDprZXk6ejZNa2pvUmhxMWpTTkpkTGlydVNYckZGeGFncXJ6dFphWEhxSEdVVEtKYmNOeXdwIn0.eyJpc3MiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCIsInN1YiI6ImRpZDprZXk6ejZNa29GcEFLaGZGclpSckJFUks4bk1OR3VSU2RoRFVyeW1iRjQxQXNLTThtOFRKIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL3B1cmwuaW1zZ2xvYmFsLm9yZy9zcGVjL29iL3YzcDAvY29udGV4dC5qc29uIl0sImlkIjoidXJuOnV1aWQ6Yjk4NDNlM2EtOTdhYi00YzZmLWFlNDMtMDFlMmM0OTVmZjY5IiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIk9wZW5CYWRnZUNyZWRlbnRpYWwiXSwibmFtZSI6IkpGRiB4IHZjLWVkdSBQbHVnRmVzdCAzIEludGVyb3BlcmFiaWxpdHkiLCJpc3N1ZXIiOnsidHlwZSI6WyJQcm9maWxlIl0sImlkIjoiZGlkOmtleTp6Nk1ram9SaHExalNOSmRMaXJ1U1hyRkZ4YWdxcnp0WmFYSHFIR1VUS0piY055d3AiLCJuYW1lIjoiSm9icyBmb3IgdGhlIEZ1dHVyZSAoSkZGKSIsInVybCI6Imh0dHBzOi8vd3d3LmpmZi5vcmcvIiwiaW1hZ2UiOiJodHRwczovL3czYy1jY2cuZ2l0aHViLmlvL3ZjLWVkL3BsdWdmZXN0LTEtMjAyMi9pbWFnZXMvSkZGX0xvZ29Mb2NrdXAucG5nIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjQtMDYtMDNUMDg6MTg6MjAuNTgyMjE0OTY5WiIsImV4cGlyYXRpb25EYXRlIjoiMjAyNS0wNi0wM1QwODoxODoyMC41ODIyOTgwMDdaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6a2V5Ono2TWtvRnBBS2hmRnJaUnJCRVJLOG5NTkd1UlNkaERVcnltYkY0MUFzS004bThUSiIsInR5cGUiOlsiQWNoaWV2ZW1lbnRTdWJqZWN0Il0sImFjaGlldmVtZW50Ijp7ImlkIjoidXJuOnV1aWQ6YWMyNTRiZDUtOGZhZC00YmIxLTlkMjktZWZkOTM4NTM2OTI2IiwidHlwZSI6WyJBY2hpZXZlbWVudCJdLCJuYW1lIjoiSkZGIHggdmMtZWR1IFBsdWdGZXN0IDMgSW50ZXJvcGVyYWJpbGl0eSIsImRlc2NyaXB0aW9uIjoiVGhpcyB3YWxsZXQgc3VwcG9ydHMgdGhlIHVzZSBvZiBXM0MgVmVyaWZpYWJsZSBDcmVkZW50aWFscyBhbmQgaGFzIGRlbW9uc3RyYXRlZCBpbnRlcm9wZXJhYmlsaXR5IGR1cmluZyB0aGUgcHJlc2VudGF0aW9uIHJlcXVlc3Qgd29ya2Zsb3cgZHVyaW5nIEpGRiB4IFZDLUVEVSBQbHVnRmVzdCAzLiIsImNyaXRlcmlhIjp7InR5cGUiOiJDcml0ZXJpYSIsIm5hcnJhdGl2ZSI6IldhbGxldCBzb2x1dGlvbnMgcHJvdmlkZXJzIGVhcm5lZCB0aGlzIGJhZGdlIGJ5IGRlbW9uc3RyYXRpbmcgaW50ZXJvcGVyYWJpbGl0eSBkdXJpbmcgdGhlIHByZXNlbnRhdGlvbiByZXF1ZXN0IHdvcmtmbG93LiBUaGlzIGluY2x1ZGVzIHN1Y2Nlc3NmdWxseSByZWNlaXZpbmcgYSBwcmVzZW50YXRpb24gcmVxdWVzdCwgYWxsb3dpbmcgdGhlIGhvbGRlciB0byBzZWxlY3QgYXQgbGVhc3QgdHdvIHR5cGVzIG9mIHZlcmlmaWFibGUgY3JlZGVudGlhbHMgdG8gY3JlYXRlIGEgdmVyaWZpYWJsZSBwcmVzZW50YXRpb24sIHJldHVybmluZyB0aGUgcHJlc2VudGF0aW9uIHRvIHRoZSByZXF1ZXN0b3IsIGFuZCBwYXNzaW5nIHZlcmlmaWNhdGlvbiBvZiB0aGUgcHJlc2VudGF0aW9uIGFuZCB0aGUgaW5jbHVkZWQgY3JlZGVudGlhbHMuIn0sImltYWdlIjp7ImlkIjoiaHR0cHM6Ly93M2MtY2NnLmdpdGh1Yi5pby92Yy1lZC9wbHVnZmVzdC0zLTIwMjMvaW1hZ2VzL0pGRi1WQy1FRFUtUExVR0ZFU1QzLWJhZGdlLWltYWdlLnBuZyIsInR5cGUiOiJJbWFnZSJ9fX19LCJqdGkiOiJ1cm46dXVpZDpiOTg0M2UzYS05N2FiLTRjNmYtYWU0My0wMWUyYzQ5NWZmNjkiLCJleHAiOjE3NDg5Mzg3MDAsImlhdCI6MTcxNzQwMjcwMCwibmJmIjoxNzE3NDAyNzAwfQ.M0rRqPWKF1zEEkFdGUPM2yvNbyuZq2gfKDtaK5YZgjNBlGFpqLKdTsj7jgc7-6oiyGeVdgoBfU1V1-NYYlcdCg",
        "disclosures": null,
        "addedOn": "2024-06-03T08:18:20.589732739Z",
        "deletedOn": null,
        "pending": false,
        "parsedDocument": {
          "@context": [
            "https://www.w3.org/2018/credentials/v1",
            "https://purl.imsglobal.org/spec/ob/v3p0/context.json"
          ],
          "id": "urn:uuid:b9843e3a-97ab-4c6f-ae43-01e2c495ff69",
          "type": [
            "VerifiableCredential",
            "OpenBadgeCredential"
          ],
          "name": "JFF x vc-edu PlugFest 3 Interoperability",
          "issuer": {
            "type": [
              "Profile"
            ],
            "id": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
            "name": "Jobs for the Future (JFF)",
            "url": "https://www.jff.org/",
            "image": "https://w3c-ccg.github.io/vc-ed/plugfest-1-2022/images/JFF_LogoLockup.png"
          },
          "issuanceDate": "2024-06-03T08:18:20.582214969Z",
          "expirationDate": "2025-06-03T08:18:20.582298007Z",
          "credentialSubject": {
            "id": "did:key:z6MkoFpAKhfFrZRrBERK8nMNGuRSdhDUrymbF41AsKM8m8TJ",
            "type": [
              "AchievementSubject"
            ],
            "achievement": {
              "id": "urn:uuid:ac254bd5-8fad-4bb1-9d29-efd938536926",
              "type": [
                "Achievement"
              ],
              "name": "JFF x vc-edu PlugFest 3 Interoperability",
              "description": "This wallet supports the use of W3C Verifiable Credentials and has demonstrated interoperability during the presentation request workflow during JFF x VC-EDU PlugFest 3.",
              "criteria": {
                "type": "Criteria",
                "narrative": "Wallet solutions providers earned this badge by demonstrating interoperability during the presentation request workflow. This includes successfully receiving a presentation request, allowing the holder to select at least two types of verifiable credentials to create a verifiable presentation, returning the presentation to the requestor, and passing verification of the presentation and the included credentials."
              },
              "image": {
                "id": "https://w3c-ccg.github.io/vc-ed/plugfest-3-2023/images/JFF-VC-EDU-PLUGFEST3-badge-image.png",
                "type": "Image"
              }
            }
          }
        },
        "manifest": null
      }
    ]
    

    The credential is now issued and automatically saved on our Wallet for later use.

    Pay attention to the credential id field. We will need it soon.

    "id": "urn:uuid:b9843e3a-97ab-4c6f-ae43-01e2c495ff69"
    

Ready to verify it? Jump to the next section.