Skip to Content
GuidesSelf Service Portal🎓 Configure a verifier application

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

Introduction

In this tutorial, you will use the Self Service 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:2024 and OID4VP.

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

Prerequisites

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

Prior knowledge

Assets

Development environment

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 Self Service 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.

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.

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.

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.

  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. This is the IACA identifying 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.

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 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. You 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, you 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:

    NEXT_PUBLIC_TENANT_URL=<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 displayed on the Application detail screen of your created application configuration.

Create credential request

Now that you have a function and a UI in place, you 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. This example uses the credential query created in the previous step.
    • challenge : Unique challenge generated by the SDK to identify this specific presentation session. You 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 : You 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 you’ve built!

Test the front channel interaction

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 access credentials

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

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

    MATTR_CLIENT_ID=ZAGvD******************************** MATTR_CLIENT_SECRET=uc8NM****************************************
  • MATTR_CLIENT_ID : Replace with the tenant’s client_id value.
  • MATTR_CLIENT_SECRET : Replace with the tenant’s 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 you are mimicking this by setting a random value as the user session ID.

You are also creating a random challenge and storing it inside a cookie to ensure that the presentation results you 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 you 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, you 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.
Last updated on