GuidesRemote (Online)πŸŽ“ mDocs tutorial

Learn how to build a web application that can verify mDocs

Introduction

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

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

Tutorial Workflow

  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:

Prerequisites

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

Prior knowledge

Assets

We recommend using the MATTR VII Postman collection in this tutorial. While this isn’t an explicit prerequisite it can really speed things up.

Testing device

  • You will need an mDoc stored on a mobile device with an application that supports ISO/IEC 18013-7:2024 (online presentation of mDocs). We recommend downloading the MATTR GO Hold example app and using it to claim an mDoc.

Development environment

Got everything? Let’s get going!

Overview

The following diagram depicts the workflow we 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.

We will build this workflow in three parts:

  1. Part 1: Setup 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: Setup the MATTR VII Verifier tenant

The MATTR VII 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 trusted wallet provider 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.

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 we must create a verifier application configuration that defines how to handle verification requests from your web application.

Request

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

HTTP
POST /v2/presentations/applications

Request body

JSON
{
    "name": "My Verifier Web Application",
    "type": "web",
    "domain": "place-holder.com",
    "openid4vpConfiguration": {
        "supportedMode": "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:
    • supportedMode : 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. We will change this setting later when we integrate a backend into the workflow.

Response

JSON
{
    "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 trusted wallet provider 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.

Request

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

HTTP
POST /v2/presentations/wallet-providers

Request body

JSON
{
    "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

JSON
{
    "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 the issuer can provide it out of band or you can obtain it via their issuer’s metadata.

Request

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

HTTP
POST /v2/credentials/mobile/trusted-issuers

Request body

JSON
{
    "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, we 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 4.1 - Create requestCredentials function
 
    // Step 5.3 - Handle same-device redirect
 
    // Step 6.3 - Create retrieveResults function
 
    // Step 3.3 - Initialize the SDK
 
    // Step 5.4 - 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 button */}
 
            {/* Step 5.5 - 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

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

Update the domain and redirect URI

Now that our tunneling service is up and running, we can update the MATTR VII verifier application configuration with the domain and redirect URI details.

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

HTTP
PUT /v2/presentations/applications/{id}

Request body

JSON
{
    "name": "My Verifier Web Application",
    "type": "web",
    "domain": "b0de-3-24-117-154.ngrok-free.app",
    "openid4vpConfiguration": {
        "supportedMode": "all",
        "redirectUris": ["https://b0de-3-24-117-154.ngrok-free.app"],
        "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
}

The request body should be identical to the original request with the exception of the following properties:

  • domain : Replace with your ngrok tunnel Forwarding URL, removing the http:// prefix.
  • redirectUris : Replace with your ngrok tunnel Forwarding URL.

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:

    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:

    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:

    tsx
    useEffect(() => {
        MATTRVerifierSDK.initialise({
            apiBaseUrl: process.env.NEXT_PUBLIC_TENANT_URL as string,
            applicationId: '0eaa8074-8cc4-41ec-9e42-072d36e2acb0'
        })
    }, [])

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

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

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:

    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
    }, [])

    This function currently includes a credentialQuery variable that defines what information is requested from the user for verification:

    This example credential query 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 names, docType or namespace will fail verification.

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

    tsx
    const options: MATTRVerifierSDK.RequestCredentialsOptions = {
        credentialQuery: [credentialQuery],
        // Step 6.2 - Use challenge from backend
        challenge: MATTRVerifierSDK.utils.generateChallenge(),
        redirectUri: window.location.origin,
        crossDeviceCallback: {
            onComplete: (callbackResponse) => {
                // Step 5.2 - Retrieve cross-device verification results
            },
            onFailure: (error) => {
                if (error.type === 'Abort') {
                    alert('The request was aborted')
                    return
                }
                alert(`There was an error processing the request - ${error.message}`)
            }
        }
    }
     
    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.
    • 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.
    • crossDeviceCallback : We will update this property later to configure how the web application handles the callback when a response is received as part of a cross-device workflow.
  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:

    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 uses 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:

    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 when they are available in cross-device workflows (onComplete of requestCredentials):

    tsx
    if ('result' in callbackResponse) {
        // Step 6.4 - Call retrieveResults function on cross-device callback
        setResults(callbackResponse.result as MATTRVerifierSDK.PresentationSessionResult)
    }
  3. Copy and paste the following code under the Step 5.3 - Handle same-device redirect comment to create a new handleRedirect function:

    tsx
    const handleRedirect = useCallback(async () => {
        const result = await MATTRVerifierSDK.handleRedirectCallback()
     
        if (result.isErr()) {
            alert(`Error retrieving results: ${result.error.message}`)
            return
        }
     
        const presentationResult = result.value.result
        if (!('sessionId' in presentationResult) || 'error' in presentationResult) {
            alert('Something went wrong')
            return
        }
     
        // Step 6.5 - Call retrieveResults function on same-device redirect
        setResults(presentationResult as MATTRVerifierSDK.PresentationSessionResult)
    }, [])

    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 (result.value.result) are stored by the web application handleRedirect function in the presentationResult variable.

  4. Copy and paste the following code under the Step 5.4 - 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:

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

    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:

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 we 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 we 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.

Make the following request to your MATTR VII tenant to update your verifier application configuration (don’t change any of the values other than setting resultAvailableInFrontChannel to false):

HTTP
PUT /v2/presentations/applications/{id}

Request body

JSON
{
    "name": "My Verifier Web Application",
    "type": "web",
    "domain": "b0de-3-24-117-154.ngrok-free.app",
    "openid4vpConfiguration": {
        "supportedMode": "all",
        "redirectUris": ["https://b0de-3-24-117-154.ngrok-free.app"],
        "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": false
}
  • resultAvailableInFrontChannel : When set to the false, the MATTR VII verifier tenant does not return the verification results directly to the front channel. Instead, it notifies the front channel that verification results for a specific presentation session are available for retrieval. In the next step we will configure your web application backend to retrieve these results based on the returned session ID.

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:

    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:
ts
import { cookies } from 'next/headers'
 
export async function POST(_request: Request) {
    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:

    ts
    import { cookies } from 'next/headers'
     
    async function getToken() {
        const response = await fetch('https://auth.au01.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 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()}`
                }
            }
        )
     
        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.

    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:

    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:

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

    This function uses the API route created above to retrieve the verification results and assign them to the presentationResults variable.

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

    tsx
    retrieveResults(callbackResponse.result.sessionId)
  5. Replace the setResults call under the Step 6.5 - 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:

    tsx
    retrieveResults(presentationResult.sessionId)

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.

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

Test the backend interaction

  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.