light-mode-image
Learn
Web

Learn how to build and configure a web application that can verify mDocs

Introduction

In this tutorial, you will use the MATTR Portal and the Verifier Web SDK to build and configure a web application that can verify an mDoc presented via an online presentation workflow as per ISO/IEC 18013-7:2025 and OID4VP.

This web application will support both same-device and cross-device workflows to accommodate flexible user journeys.

Tutorial Workflow](/images/tutorial-same-device-workflow.svg)

  1. The user interacts with your web application on their mobile device browser.
  2. The user is asked to present information as part of the interaction.
  3. The user is redirected to their wallet application.
  4. The wallet application authenticates the user.
  5. The user is informed of what information they are about to share and provide their consent.
  6. The user is redirected back to the web application where verification results are displayed, enabling them to continue with the interaction.

The result will look something like this:

Tutorial Workflow](/images/tutorial-cross-device-workflow.svg)

  1. The user interacts with your web application on their desktop browser.
  2. The user is asked to present information as part of the interaction.
  3. The user scans a QR code using a mobile device with an installed wallet application.
  4. The wallet application is launched on the mobile device.
  5. The wallet application authenticates the user.
  6. The user is informed of what information they are about to share and provide their consent.
  7. Verification results are displayed in the user's desktop browser, enabling them to continue with the interaction.

The result will look something like this:

Prerequisites

Before you get started, let's make sure you have everything you need.

Prior knowledge

Assets

Development environment

  • Download and install npm.
  • To test locally you will need to expose your web application to the internet. You can do this by setting up a free ngrok account, using a cloudflare tunnel or using your own solution. For this tutorial we will be using ngrok. Sign up for a free account at ngrok.com.
  • You will need a code editor such as VS Code.

Got everything? Let's get going!

Overview

The following diagram depicts the workflow you will build in this tutorial:

  1. The user triggers the workflow by interacting with the web application.
  2. The web application uses the embedded Verifier Web SDK capabilities to start a presentation-based verification session with the configured MATTR VII tenant.
  3. The MATTR VII tenant responds with a link to invoke a matching mobile application.
  4. The web application uses the link to invoke a matching mobile application, by either using a redirect (same-device) or rendering a QR code (cross-device).
  5. The mobile application makes a request to the MATTR VII tenant to retrieve a request object, defining what information is requested for verification.
  6. The MATTR VII tenant returns the request object to the mobile application.
  7. The mobile application (upon user consent) returns an authorization response to the MATTR VII tenant, which includes the information required for verification.
  8. The MATTR VII tenant returns the verification results to the web application.
  9. The web application surfaces the verification results to the user and the interaction continues.

You will build this workflow in three parts:

  1. Part 1: Use the MATTR Portal to configure the MATTR VII verifier tenant.

  2. Part 2: Build a web application with mDocs verification capabilities.

  3. Part 3: Integrate a backend (optional).

Part 1: Configure the MATTR VII verifier tenant

The MATTR VII verifier tenant will be used to interact with your web application (generating a verification request) and the wallet application (presenting an mDoc for verification) as per OID4VP and ISO/IEC 18013-7. To enable this, you must:

  1. Create a verifier application configuration: Define what applications can create verification sessions with the MATTR VII tenant, and how to handle these requests.
  2. Create a supported wallet configuration: Define how to invoke specific wallet applications as part of an online verification workflow.
  3. Configure a trusted issuer: The MATTR VII verifier tenant will only accept mDocs issued by these trusted issuers.

You can perform these steps via the Portal or by making API requests to your MATTR VII tenant.

Create a verifier application configuration

Each MATTR VII tenant can interact with multiple verifier applications, and can handle requests differently for each application. This means you must create a verifier application configuration that defines how to handle verification requests from your web application.

  1. Expand the Credential Verification section in the left-hand navigation panel.
  2. Select Applications.
  3. Select the Create new button.
  4. Use the Name text box to insert a meaningful and friendly name for your application.
  5. Use the Type radio button to select Web.
  6. Use the Allowed domains text box to insert a placeholder domain name (e.g. place-holder.com). This indicates domain names that the MATTR VII tenant can verify incoming requests are from known and trusted applications. You will update this placeholder value later.
  7. Use the Redirect URIs text box to insert a placeholder redirect URI (e.g. https://place-holder.com). This is the URI the user would be redirected to after presenting a credential on a same-device flow. You will update this placeholder value later.
  8. Select the Create button to create the new application and display the Application detail screen.
  9. Copy and record the ID value. You will use it later in the tutorial.

Make the following request to your MATTR VII tenant to create a verifier application configuration:

Request
POST /v2/presentations/applications
Request body
{
  "name": "My Verifier Web Application",
  "type": "web",
  "domain": "place-holder.com",
  "openid4vpConfiguration": {
    "supportedModes": "all",
    "redirectUris": ["https://place-holder.com"],
    "display": {
      "logoImage": {
        "url": "https://static.mattr.global/logos/mattr/s/gFC.svg",
        "altText": "MATTR Logo image"
      },
      "headerText": "Share your information.",
      "bodyText": "Please scan the QR code to provide information required for completing this interaction."
    }
  },
  "resultAvailableInFrontChannel": true
}
  • name : You can use whatever name you'd like, as long as it is unique on your tenant.
  • type : We will use web as we are building a web application.
  • domain : Here we define the web application domain name so that the MATTR VII tenant can verify incoming requests are from known and trusted applications. As localhost is not supported we will use a tunneling service (ngrok) to test this tutorial. We will update this placeholder value later.
  • openid4vpConfiguration:
    • supportedModes : Setting this to all indicates that your web application will support both same-device and cross-device workflows.
    • redirectUris : This is the URI the user would be redirected to after presenting a credential on a same-device flow. We will update this placeholder value later.
    • display : Adjust the iframe modal appearance in cross-device flows:
      • logoImage :
        • url : Insert any publicly available URL of a logo image which is displayed on the top left corner of the iframe modal.
        • altText : Edit the logo image alternative text.
      • headerText : Edit the header displayed in the iframe modal.
      • bodyText : Edit the text displayed in the iframe modal.
  • resultAvailableInFrontChannel : Setting this to true makes the verification results available directly to the web application. You can change this setting later if you choose to integrate a backend into the workflow.

Response

Response body
{
  "id": "0eaa8074-8cc4-41ec-9e42-072d36e2acb0", 
  "name": "My Verifier Web Application"
  //... rest of application configuration
}
  • id : We will use this value later to initialize the SDK so that requests coming from your web application can be recognized and trusted by the MATTR VII tenant.

Create a supported wallet configuration

Verifier web applications can define specific wallet applications to accept mDocs from as part of their verification workflows. The MATTR VII verifier tenant needs to be configured with a specific URI scheme that will be used to invoke these wallets.

  1. Expand the Credential Verification section in the left-hand navigation panel.
  2. Select Supported wallets.
  3. Select the Create new button.
  4. Use the Name text box to insert a meaningful and friendly name for your wallet application (for example "MATTR GO Hold").
  5. Use the Authorize endpoint text box to insert mdoc-openid4vp://. This is the URI scheme that will be used to invoke the wallet application. You can learn more about URI scheme selection logic here.
  6. Select the Create button to create the new wallet configuration.

The authorization endpoint configured in the example above (mdoc-openid4vp://) is the default OID4VP scheme. While this is technically redundant, we chose to include this step to explain how to adjust this configuration for wallet applications using different schemes.

Make the following request to your MATTR VII tenant to create a trusted wallet provider configuration:

Request
POST /v2/presentations/wallet-providers
Request body
{
  "name": "MATTR GO Hold", 
  "openid4vpConfiguration": {
    "authorizationEndpoint": "mdoc-openid4vp://"
  }
}
  • name : Unique name to identify this trusted wallet provider.
  • authorizationEndpoint : URI scheme that will be used to invoke the wallet application. You can learn more about URI scheme selection logic here.

The authorizationEndpoint configured in the example above (mdoc-openid4vp://) is the default OID4VP scheme. While this is technically redundant, we chose to include this step to explain how to configure this endpoint for wallet application using different schemes.

Response

Response body
{
  "id": "99890c34-e4b7-4a23-84d6-e5de57114c00",  
  "name": "MATTR GO Hold",
  "openid4vpConfiguration": {
    "authorizationEndpoint": "mdoc-openid4vp://"
  }
}
  • id : We will use this value later to indicate this is the wallet the web application expects to receive mDocs from.

Configure a trusted issuer

You must configure trusted issuers on your MATTR VII verifier tenant, as presented mDocs will only be verified if they had been issued by a trusted issuer.

This is achieved by providing the PEM certificate of the IACA used by these issuers to sign mDocs. In production environments this certificate can be retrieved in one of two ways:

  • The issuer can provide it directly to the verifier out of band.

  • The verifier can retrieve it from the issuer's metadata:

    1. Make a GET request to the issuer's /.well-known/openid-credential-issuer endpoint.
    2. Retrieve the mdoc_iacas_uri element from the response.
    3. Make a GET request to that URI to retrieve the IACAs list (for example for a MATTR VII Issuer tenant this URI would be {tenant_url}/v1/openid/iacas).
  1. Expand the Credential Verification section in the left-hand navigation panel.
  2. Select Trusted issuers.
  3. Select the Create new button.
  4. Use the Certificate PEM file to upload the following file (ensure you extract the ZIP file and upload the .pem file itself). This is the IACA certificate that identifies the MATTR Labs demo issuer which issues the credential referenced in the prerequisites section.
  5. Select the Add button to add the new trusted issuer.

This completes the configuration of the MATTR VII verifier tenant. In the next step, you will build a simple application that you can run locally to test the verification workflow and ensure everything is functioning as expected.

Make the following request to your MATTR VII tenant to configure a truster issuer:

Request
POST /v2/credentials/mobile/trusted-issuers
Request body
{
  "certificatePem": "-----BEGIN CERTIFICATE-----\nMIICYzCCAgmgAwIBAgIKXhjLoCkLWBxREDAKBggqhkjOPQQDAjA4MQswCQYDVQQG\nEwJBVTEpMCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0Ew\nHhcNMjQwMTE4MjMxNDE4WhcNMzQwMTE1MjMxNDE4WjA4MQswCQYDVQQGEwJBVTEp\nMCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0EwWTATBgcq\nhkjOPQIBBggqhkjOPQMBBwNCAASBnqobOh8baMW7mpSZaQMawj6wgM5e5nPd6HXp\ndB8eUVPlCMKribQ7XiiLU96rib/yQLH2k1CUeZmEjxoEi42xo4H6MIH3MBIGA1Ud\nEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRFZwEOI9yq\n232NG+OzNQzFKa/LxDAuBgNVHRIEJzAlhiNodHRwczovL21vbnRjbGlmZi1kbXYu\nbWF0dHJsYWJzLmNvbTCBgQYDVR0fBHoweDB2oHSgcoZwaHR0cHM6Ly9tb250Y2xp\nZmYtZG12LnZpaS5hdTAxLm1hdHRyLmdsb2JhbC92Mi9jcmVkZW50aWFscy9tb2Jp\nbGUvaWFjYXMvMjk0YmExYmMtOTFhMS00MjJmLThhMTctY2IwODU0NWY0ODYwL2Ny\nbDAKBggqhkjOPQQDAgNIADBFAiAlZYQP95lGzVJfCykhcpCzpQ2LWE/AbjTGkcGI\nSNsu7gIhAJfP54a2hXz4YiQN4qJERlORjyL1Ru9M0/dtQppohFm6\n-----END CERTIFICATE-----"
}

If you intend to test this tutorial with a credential different than the one recommended in our Testing device prerequisite, replace the certificatePem value with your own issuer's IACA.

Response

A successful 201 response indicates that this issuer's certificate was added to your MATTR VII tenant's trusted issuer's list. This means that mDocs that use this IACA as their root certificate can be trusted and verified.

Part 2: Build a web application with mDocs verification capabilities

Now that the MATTR VII verifier tenant is properly configured, you can proceed with the steps required to embed verification capabilities into your web application:

  1. Setup your environment: Setup the required infrastructure for your web application.
  2. Update the domain and redirect URI: Once your tunneling service is up and running you can update the placeholder values in the MATTR VII verifier application configuration.
  3. Initialize the SDK: So that the SDK functions are available in your web application.
  4. Create a credential query: Define what information is required for verification and how to handle the interaction with the user.
  5. Handle verification results: Create the logic that enables your web application to retrieve verification results in different workflows.

Setup your environment

  1. Open your terminal and create a new NextJS default project:
BASH
npx create-next-app@latest --src-dir --yes
  1. Approve installing any required packages.
  2. Insert a name for your project (e.g. my-verifier-web-application).
  3. Open the project app's folder:
BASH
cd my-verifier-web-application
  1. Install the SDK:
BASH
npm install @mattrglobal/verifier-sdk-web
  1. Open the src/app/page.tsx file in your preferred code editor and replace all existing code with the following:
tsx
"use client";

// Step 3.2 - Add dependencies

export default function Home() {
  // Step 5.1 - Store results state

  // Step 6.1 - Create createRequest function

  // Step 6.3 - Create retrieveResults function

  // Step 4.1 - Create requestCredentials function

  // Step 5.4 - Handle same-device redirect

  // Step 3.3 - Initialize the SDK

  // Step 5.5 - Add effect to handle redirect

  return (
    <div className="max-w-2xl mt-12 mx-auto px-8">
      <h1 className="text-3xl font-bold my-8">
        mDocs Online Verification Tutorial
      </h1>
      {/* Step 4.3 - Add request credentials button */}

      {/* Step 5.6 - Render results */}
    </div>
  );
}

This will serve as the basic structure for your application. We will copy and paste different code snippets into specific locations to achieve the different functionalities. These locations are indicated by comments that reference the corresponding tutorial step (e.g. // Step 3.2 - Add dependencies).

We recommend copying and pasting the comment text to easily locate it in the code.

  1. Run the project:
BASH
npm run dev

This will run the app and make it available at http://localhost:3000 (or the next available port if 3000 is already used).

  1. Start an ngrok tunnel in a new terminal window:
BASH
ngrok http http://localhost:3000

If your web application is running on a port different than 3000, use that value in the ngrok command above.

Your terminal window should show the tunnel details:

ngrok tunnel](/images/ngrok-tunnel.png)

  1. Make note of the Forwarding value, we will use it in the next step.

Update the domain and redirect URI

With the tunneling service now active, update the MATTR VII verifier application configuration to include the domain and redirect URI details.

  1. Expand the Credential Verification section in the left-hand navigation panel.

  2. Select Applications.

  3. Select the verifier application configuration you created in the previous step.

  4. Update the Allowed domains text box to include the Forwarding URL from the ngrok tunnel, removing the https:// prefix.

  5. Update the Redirect URIs text box to include the Forwarding URL from the ngrok tunnel.

  6. Select the Update button to update the application configuration.

    The Forwarding URL is a temporary URL that will be used to test the web application. It will change every time you restart the ngrok tunnel, so you will need to repeat this step each time you restart the tunnel.

Initialize SDK

  1. Create a new file named .env.local in the projects root folder and add the following code to add your tenant's URL to the project's variables:

    .env.local
    NEXT_PUBLIC_TENANT_URL=https://learn.vii.au01.mattr.global
    • NEXT_PUBLIC_TENANT_URL : Replace the parameter value with your tenant_url.
  2. Copy and paste the following code under the Step 3.2 - Add dependencies comment to add required dependencies to your web application:

    src/app/page.tsx
    import * as MATTRVerifierSDK from "@mattrglobal/verifier-sdk-web";
    import { useCallback, useEffect, useState } from "react";
  3. Copy and paste the following code under the Step 3.3 - Initialize the SDK comment to create a new function that will be used by your web application to initialize the SDK:

    src/app/page.tsx
    useEffect(() => {
      MATTRVerifierSDK.initialize({
        apiBaseUrl: process.env.NEXT_PUBLIC_TENANT_URL as string,
        applicationId: "0eaa8074-8cc4-41ec-9e42-072d36e2acb0",
      });
    }, []);

    The function calls the SDK's initialize function that makes the SDK methods available in your web application while defining the following parameters:

    • apiBaseUrl: This value is retrieved from the NEXT_PUBLIC_TENANT_URL project variable defined above. It indicates to the Verifier Web SDK what MATTR VII tenant to interact with in order to verify presented mDocs.
    • applicationId : Replace with the id from the response returned when you created the verifier application configuration. This will be used by the MATTR VII tenant to identify verification requests from your web application.

Create credential request

Now that we have a function and a UI in place, we need to define what the function does by creating a credential request. This request will define what information is requested from the user, and how to interact with the MATTR VII verifier tenant.

  1. Copy and paste the following code under the Step 4.1 - Create requestCredential function comment to create a new requestCredentials function:

    src/app/page.tsx
    const requestCredentials = useCallback(async () => {
      const credentialQuery = {
        profile:
          MATTRVerifierSDK.OpenidPresentationCredentialProfileSupported.MOBILE,
        docType: "org.iso.18013.5.1.mDL",
        nameSpaces: {
          "org.iso.18013.5.1": {
            age_over_18: {},
            given_name: {},
            family_name: {},
            portrait: {},
          },
        },
      } as MATTRVerifierSDK.CredentialQuery;
    
      // Step 4.2 - Add credential request
    
      // Step 5.2 - Retrieve cross-device verification
    
      // Step 5.3 - Add setResults
    }, []);

    The function includes a credentialQuery variable that defines what information is requested from the user for verification. This example will request the age_over_18, given_name, family_name and portrait claims from a credential of profile mobile and docType org.iso.18013.5.1.mDL, under the org.iso.18013.5.1 namespace. Presenting a credential with different claims, profile, docType or namespace will fail verification.

  2. Copy and paste the following code under the Step 4.2 - Add credential request comment with your own walletProviderId so that the new requestCredentials function calls the SDK's requestCredentials function, passing the credentialQuery variable as a parameter:

    src/app/page.tsx
    const options: MATTRVerifierSDK.RequestCredentialsOptions = {
      credentialQuery: [credentialQuery],
      // Step 6.2 - Use challenge from backend
      challenge: MATTRVerifierSDK.utils.generateChallenge(),
      openid4vpConfiguration: {
        redirectUri: window.location.origin,
        walletProviderId: "99890c34-e4b7-4a23-84d6-e5de57114c00",
      },
    };
    
    const results = await MATTRVerifierSDK.requestCredentials(options);
    • options : This object defines the required parameters for calling the SDK's requestCredentials function. It is passed as a parameter to the function when calling it.
    • credentialQuery : Defines what information is requested from the user for verification. In our example we are using the credential query created in the previous step.
    • challenge : Unique challenge generated by the SDK to identify this specific presentation session. We will use it in part 3 of the tutorial.
    • openid4vpConfiguration: This object defines the configuration necessary for an OID4VP presentation flow:
      • redirectUri : Indicates that the user will be redirected to the same web application screen when completing a same-device workflow. It can be any other path on your application domain, as long as it is handled by the application.
      • walletProviderId : Replace with the id of the trusted wallet provider that you configured above.
  3. Copy and paste the following code under the Step 4.3 - Add request credentials button comment to create a new button that the user will use to interact with the web application:

    src/app/page.tsx
    <button
      onClick={requestCredentials}
      className="px-4 py-2 rounded border-2 border-black font-bold"
    >
      Request Credential
    </button>

    Once the user selects this button, the web application will trigger the verification workflow by calling the requestCredentials function created above:

    • This function calls the SDK's requestCredentials method which starts a presentation session with the configured MATTR VII session.
    • Once a session is established, the MATTR VII verifier tenant will interact with the user's wallet application to request and verify the details provided in the query.
    • When the verification process is complete, the MATTR VII verifier tenant will return the verification results to your web application.

Handle verification results

  1. Copy and paste the following code under the Step 5.1 - Store results state comment to create a variable that will store the verification state:

    src/app/page.tsx
    const [results, setResults] =
      useState<MATTRVerifierSDK.PresentationSessionResult | null>(null);
  2. Copy and paste the following code under the Step 5.2 - Retrieve cross-device verification results comment to retrieve verification results in cross-device workflows:

    src/app/page.tsx
    if (results.isOk()) {
      // Step 6.4 - Call retrieveResults function in cross-device workflow
      setResults(
        results.value.result as MATTRVerifierSDK.PresentationSessionResult,
      );
    } else {
      alert(`Error retrieving results: ${results.error.message}`);
    }
  3. Add setResults in the square brackets under the // Step 5.3 - Add setResults to set the results state variable once results are available. Your code should look as follows:

    src/app/page.tsx
    }, [setResults])
  4. Copy and paste the following code under the Step 5.4 - Handle same-device redirect comment to create a new handleRedirect function:

    src/app/page.tsx
    const handleRedirect = useCallback(async () => {
      const results = await MATTRVerifierSDK.handleRedirectCallback();
    
      if (results.isOk()) {
        // Step 6.6 - Call retrieveResults function on same-device redirect
        if (results.value.result) {
          setResults(results.value.result);
        }
      } else {
        alert(`Error retrieving results: ${results.error.message}`);
        return;
      }
      // Step 6.7 - Replace setResults function with retrieveResults function
    }, [setResults]);

    This function calls the SDK's handleRedirectCallback function, which returns the verification results as the user is redirected following a same-device presentation workflow. These results (results.value.result) are then assigned to the results state variable by setResults.

  5. Copy and paste the following code under the Step 5.5 - Add effect to handle redirect comment to add an effect that calls the handleRedirect function when the web application is refreshed with a hash, indicating a redirect after completing a same-device presentation workflow:

    src/app/page.tsx
    useEffect(() => {
      if (window.location.hash) {
        handleRedirect();
      }
    }, [handleRedirect]);
  6. Copy and paste the following code under the Step 5.6 - Render results comment to retrieve properties of the results object once it is available, and display them to the user as part of the interaction:

    src/app/page.tsx
    <h2 className="text-xl font-bold mt-8 mb-4">Results</h2>
    {
      !results && "No results available"
    }
    {
      results && "credentials" in results && (
        <div className="flex flex-col gap-2">
          <div className="flex flex-row">
            <div className="w-1/3 font-bold">Verification Result</div>
            <div className="w-2/3">
              {results.credentials?.[0].verificationResult.verified
                ? "verified"
                : "not verified"}
            </div>
          </div>
          <div className="flex flex-row">
            <div className="w-1/3 font-bold">Valid Until</div>
            <div className="w-2/3">
              {results.credentials?.[0].validityInfo?.validUntil}
            </div>
          </div>
          <div className="flex flex-row">
            <div className="w-1/3 font-bold">Issuer</div>
            <div className="w-2/3">
              {results.credentials?.[0].issuerInfo?.commonName}
            </div>
          </div>
          <div className="flex flex-row">
            <div className="w-1/3 font-bold">Given Name</div>
            <div className="w-2/3">
              {(results.credentials?.[0].claims?.["org.iso.18013.5.1"].given_name
                .value as string) ?? ""}
            </div>
          </div>
          <div className="flex flex-row">
            <div className="w-1/3 font-bold">Age over 18</div>
            <div className="w-2/3">
              {results.credentials?.[0].claims?.["org.iso.18013.5.1"].age_over_18
                .value
                ? "Yes"
                : "No"}
            </div>
          </div>
        </div>
      );
    }
    {
      results && "error" in results && (
        <div className="flex flex-row">
          <div className="w-1/3 font-bold">{results.error.type}</div>
          <div className="w-2/3">{results.error.message}</div>
        </div>
      );
    }

    In this example when verification is successful ("credentials" in results) the app retrieves and displays the following properties of the first credential (credentials?.[0]) of the results object:

    • verificationResult.verified : Indicates whether or not the mDoc was verified.
    • validityInfo.validUntil : Indicates the mDoc expiry date.
    • issuerInfo.commonName : Indicates the issuer's name.
    • claims?.['org.iso.18013.5.1'].given_name : Indicates the holder's given name.
    • claims?.['org.iso.18013.5.1'].age_over_18.value : Indicates whether or not the holder is over 18.

    When verification fails ("error" in results) the app retrieves and displays error details (error.type and error.message) from the results response.

Your web application will now continue the interaction based on the workflow type:

  • For cross-device workflows, the web application will display the verification results in the desktop browser where the interaction started. The user can then continue the interaction on this desktop browser.
  • For same-device workflows, the MATTR VII verifier tenant will redirect the user back to the web application in their mobile browser, where verification will be displayed. The user can then continue the interaction on this mobile browser.

Congratulations, your web application is now ready to verify mDocs.

Let's test what we've built!

Test the front channel interaction

  1. Open your ngrok forwarding URL in a browser on a mobile device where the MATTR GO Hold example app is installed.
  2. Select the Request Credential button.
  3. Select Allow to open the GO Hold app.
  4. The app should launch and display what information is requested for verification.
  5. Select Start.
  6. Select Confirm to share a credential which includes the requested information (claim one here if you haven't done so already).
  7. The web application should display the verification results as a string.

The result should look something like this:

  1. In a desktop browser, open your public ngrok forwarding URL (Do not use localhost as the cross-device flow requires a publicly accessible URL).
  2. Select the Request Credential button. A QR code will be displayed.
  3. Open the camera on a mobile device where MATTR GO Hold is installed and scan the QR code.
  4. Confirm opening the QR code with the GO Hold app.
  5. The app should launch and display what information is requested for verification.
  6. Select Start.
  7. Select Confirm to share a credential which includes the requested information (claim one here if you haven't done so already).
  8. Back on your desktop browser you the web application should display the verification results as a string.

The result should look something like this:

If your goal was just to see mDocs online verification in action, there's no need to proceed to the next step of the tutorial. However, production solutions often integrate a backend, and if you're curious about how that works, feel free to continue to the next step—it's optional and won't change the overall workflow, just what’s happening behind the scenes.

Part 3: Integrate a backend into your online verification workflow

Our online verification workflow is now working well. However, to make the solution more secure it is recommend to integrate your web application backend into the workflow. In this adjusted workflow, verification results are only shared with the backend and only surfaced to the front channel after validation. Refer to the detailed workflow page for more information.

In this part of the tutorial you will configure a simple backend that would retrieve the verification results from the MATTR VII tenant, validate and pass them to the front channel web application.

Update verifier application configuration

The first thing you need to do is to make sure verification results are not returned directly to the web application's front channel. This setting is part of your MATTR VII verifier application configuration.

  1. Expand the Credential Verification section in the left-hand navigation panel.

  2. Select Applications.

  3. Select the verifier application configuration you created in the previous step.

  4. Use the Verification results return method radio button to select Must be retrieved and validated by backend.

  5. Select the Update button to update the application configuration.

    This setting is only available when the application type is set to Web.

Add your MATTR VII login details

Your backend will need to make a request to a protected MATTR VII endpoint to retrieve the verification results. To enable this, we must provide it with your MATTR VII tenant login details.

  1. Add the following code to your .env.local file to add your MATTR VII tenant login details:

    .env.local
    MATTR_CLIENT_ID=ZAGvD********************************
    MATTR_CLIENT_SECRET=uc8NM****************************************
  • MATTR_CLIENT_ID : Replace with your client_id value.
  • MATTR_CLIENT_SECRET : Replace with your client_secret value.

Create a session and associate a challenge

The web application backend needs to generate a unique challenge in the context of an existing user session. In a typical application, the user would likely already interact with the application inside an active session, and often that session is tracked using a cookie. In this tutorial we are mimicking this by setting a random value as the user session ID.

We are also creating a random challenge and storing it inside a cookie to ensure that the presentation results we are retrieving belong to the user in the current user session.

  1. Create a new file at src/app/api/request/route.ts with the following content:
src/app/api/request/route.ts
import { cookies } from "next/headers";

export async function POST() {
  const sessionId = crypto.randomUUID();
  const challenge = crypto.randomUUID();

  const cookieStore = await cookies();
  cookieStore.set("session_id", sessionId, {
    httpOnly: true,
    secure: true,
    sameSite: true,
  });
  cookieStore.set("challenge", challenge, {
    httpOnly: true,
    secure: true,
    sameSite: true,
  });

  return Response.json({ challenge }, { status: 201 });
}

While it's not strictly necessary to keep the challenge secret, you might want to only use a single encrypted session cookie to not leak any information about your session management.

If you use a database to store the sessions state, you could use that database to store the challenge against the user session.

Retrieve verification results

Next we need to create an API route that will make a request to the MATTR VII verifier tenant and retrieve the results of a specific presentation session based on its ID.

  1. Create a new file at src/app/api/results/[id]/route.ts with the following content:

    src/app/api/results/[id]/route.ts
    import { cookies } from "next/headers";
    
    async function getToken(tenantUrl: string) {
      const region = tenantUrl.split(".")[2];
      const response = await fetch(
        `https://auth.${region}.mattr.global/oauth/token`,
        {
          method: "POST",
          headers: {
            "content-type": "application/json",
          },
          body: JSON.stringify({
            client_id: process.env.MATTR_CLIENT_ID,
            client_secret: process.env.MATTR_CLIENT_SECRET,
            audience: process.env.NEXT_PUBLIC_TENANT_URL,
            grant_type: "client_credentials",
          }),
        },
      );
    
      const { access_token } = await response.json();
      return access_token;
    }
    
    export async function GET(
      _: Request,
      { params }: { params: Promise<{ id: string }> },
    ) {
      const tenantUrl = process.env.NEXT_PUBLIC_TENANT_URL;
      if (!tenantUrl) {
        return Response.json({ error: "No tenant URL configured" });
      }
    
      const id = (await params).id;
      const cookiesStore = await cookies();
    
      if (!cookiesStore.get("session_id")) {
        return Response.json(
          { error: "No valid session for user" },
          { status: 401 },
        );
      }
    
      const response = await fetch(
        `${process.env.NEXT_PUBLIC_TENANT_URL}/v2/presentations/sessions/${id}/result`,
        {
          headers: {
            authorization: `Bearer ${await getToken(tenantUrl)}`,
          },
        },
      );
    
      const results = await response.json();
    
      if (results.challenge !== cookiesStore.get("challenge")?.value) {
        return Response.json({ error: "Challenge does not match" });
      }
    
      return Response.json({ results });
    }

    The getToken function first uses project variables to retrieve a MATTR VII access token, as the results are retrieved from a protected endpoint.

    The route must be called with a dynamic id route parameter. This is the unique identifier of the presentation session. It is used in the authenticated request to the /v2/presentations/sessions/${id}/result endpoint to retrieve the corresponding verification results.

    Your backend must then validate the returned results. The exact implementation would depend on your user session management, but you should always ensure that:

    • The request to retrieve the results comes from an active user session (as per its session_id).
    • The challenge included in the response from the MATTR VII tenant matches the challenge you have stored for that active user session.

Adjust front channel to retrieve results via backend

Now that the backend is configured to retrieve the verification results, we need to adjust the front channel web application so that it can retrieve these results from the backend.

  1. Copy and paste the following code under the Step 6.1 - Create createRequest function comment.

    src/app/page.tsx
    async function createRequest() {
      const response = await fetch("/api/request", { method: "POST" });
      const { challenge } = await response.json();
      return challenge;
    }
  2. Replace the challenge assignment under the Step 6.2 - Use challenge from backend comment with the following code to call the createRequest function to generate a challenge:

    src/app/page.tsx
          challenge: await createRequest(),
  3. Add the following code under the Step 6.3 - Create retrieveResults function comment to create a new function that retrieves the verification results:

    src/app/page.tsx
    const retrieveResults = useCallback(
      async (id: string) => {
        const response = await fetch(`api/results/${id}`);
        const { results } = await response.json();
        setResults(results as MATTRVerifierSDK.PresentationSessionResult);
      },
      [setResults],
    );

    This function uses the API route created above to retrieve the verification results and use setResults to assign them to the results state variable

  4. Replace the setResults call under the Step 6.4 - Call retrieveResults function in cross-device workflow with the following code to call the new retrieveResults function in the callback of a cross-device workflow:

    src/app/page.tsx
    retrieveResults(results.value.sessionId);
  5. Replace the setResults function in the square brackets under the // Step 5.3 - Add setResults comment with the retrieveResults function. Your code should look like this:

    src/app/page.tsx
    }, [retrieveResults]) // requestCredentials dependency array
  6. Replace the whole conditional around the setResults call under the Step 6.6 - Call retrieveResults function on same-device redirect with the following code to call the new retrieveResults function when the user is redirected to the app following a same-device workflow:

    src/app/page.tsx
    retrieveResults(results.value.sessionId);
  7. Replace the setResults function in the brackets under the // Step 6.7 - Replace setResults function with retrieveResults function comment with the retrieveResults function. The line should look like this:

    src/app/page.tsx
    }, [retrieveResults])

Your web application now has an integrated backend that can validate verification results before passing them to the front channel, making the solution more secure.

Test the backend interaction

Let's test to ensure everything is working as expected.

  1. Repeat the steps in the front channel testing step above.
  2. The interaction should look the same, but behind the scenes it will actually be handled by your backend.

What's next?

  • This project was run and tested locally using tunneling services. To build similar online verification capabilities into a production web application, all you need to do is replace the domain and redirectUri with the corresponding values of your production web application.
  • This project was created to showcase happy paths. It is recommended to review our comprehensive SDK reference documentation to learn more about available functions and classes.
  • As always, feel free to reach out if you have any questions or encounter any issues we can help with.

How would you rate this page?