GuidesReact Native Holder SDK🎓 Online presentation

Learn how to build a React Native iOS and Android application that can present an mDoc online

Introduction

In this tutorial you will use the React Native Holder SDK to build iOS and Android applications that can present a claimed mDoc to a verifier remotely via an online presentation workflow as per ISO/IEC 18013-7:2024 and OID4VP.

These applications will support both same-device and cross-device workflows to accommodate flexible user journeys:

Tutorial Workflow

  1. The user interacts with a website on their mobile device browser.
  2. The user is asked to present information as part of the interaction.
  3. The user is redirected to the application you will build in this tutorial.
  4. The 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 browser where verification results are displayed, enabling them to continue with the interaction.

The result will look something like this:

Prerequisites

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

Prior knowledge

If you need to get a holding solution up and running quickly with minimal development resources and in-house domain expertise, talk to us about our white-label MATTR GO Hold app which might be a good fit for you.

Assets

  • You will need access to the SDK and additional MATTR dependencies to complete this tutorial. Contact us if you are interested in trialing the SDK.
💡

This tutorial is intended for use with the latest versions of MATTR’s React Native Holder SDK and the React Native Mobile Credential Holder SDK extension.

Development environment

This tutorial uses Expo Go, leveraging Development Builds. Due to MATTR SDK dependency requirements, the tutorial is using React Native v0.73 and Expo SDK v50.

Prerequisite tutorial

Testing devices

  • Supported iOS and/or Android device to run the built application on, setup with:
    • Biometric authentication.
    • Bluetooth access and Bluetooth turned on.
    • Available internet connection.

Tutorial steps

To enable a user to present a stored mDoc to a verifier via an online presentation workflow, you will build the following capabilities into your application:

  1. Register the verifier’s authorization endpoint.
  2. Create an online presentation session.
  3. Handle a presentation request.
  4. Send a presentation response.

Register the authorization endpoint

The authorization endpoint is a URI associated with a application identifier in the MATTR VII tenant configuration. It is used to invoke an application that will handle the presentation request. The application then uses the URI to retrieve a request object, which details what information is requested for verification.

Verifier applications are recommended to generate this URI as a Universal link, as this enables them to explicitly define and validate applications that can respond to their verification requests.

However, for simplicity reasons in this tutorial our verifier application is generating the URI using the default custom URI scheme defined by the OID4VP specification (mdoc-openid4vp). This means that you need to configure the application to be able to handle this custom URI scheme.

  1. Update the scheme property assignment in the app.config.ts file under the // Online presentation - Step 1.1: Update application custom scheme comment and replace the existing assignment with the following:

    app.config.ts
    scheme: "mdoc-openid4vp",
  2. Delete the ios and android directories in the project’s root. This is required to ensure the scheme changes are applied correctly.

Create an online presentation session

Now that the application can handle an OID4VP custom URI scheme, the next step is to build the capability to use the request URI to retrieve the request object, which includes the following information:

  • What credentials are requested for verification.
  • What specific claims are required from these credentials.
  • What MATTR VII tenant to interact with.

This information is then used by the application to create an online presentation session by calling the SDK’s createOnlinePresentationSession function.

  1. In your project’s app directory, create a new file named online-presentation.tsx and add the following scaffolding code:

    app/online-presentation.tsx
    // Step 3.2: Import Credential selector component
    import { useWallet } from "@/providers/WalletProvider";
    import type { OnlinePresentationSession } from "@mattrglobal/mobile-credential-holder-react-native";
    import { useGlobalSearchParams, useRouter } from "expo-router";
    import React, { useState, useEffect, useCallback } from "react";
    import { Alert, StyleSheet, Text, TouchableOpacity, View } from "react-native";
     
    export default function OnlinePresentation() {
      const router = useRouter();
      const { scannedValue: authorisationRequestUri } = useGlobalSearchParams<{ scannedValue: string; }>();
      const { wallet } = useWallet();
     
      const [onlinePresentationSession, setOnlinePresentationSession] = useState<OnlinePresentationSession | null>(null);
      const [error, setError] = useState<string | null>(null);
      const [requests, setRequests] = useState<OnlinePresentationSession["matchedCredentials"]>([]);
      const [selectedCredentialIds, setSelectedCredentialIds] = useState<string[]>([]);
     
      const handleError = useCallback((message: string) => {
        setError(message);
        console.error(message);
      }, []);
     
     
      // Step 3.3: Add handleToggleSelection function
     
      // Step 2.6: Create Online Presentation Session
     
      // Step 4.1: Add handleSendResponse function
     
      if (error) {
        return (
          <View style={styles.container}>
            <Text style={styles.errorText}>Error: {error}</Text>
          </View>
        );
      }
     
      if (!onlinePresentationSession) {
        return (
          <View style={styles.container}>
            <Text>No online presentation session</Text>
          </View>
        );
      }
     
      // Step 3.4: Display presentation session details
    }
     
    const styles = StyleSheet.create({
        container: {
            flex: 1,
            padding: 16,
        },
        errorText: {
            color: "red",
            fontSize: 16,
        },
        button: {
            backgroundColor: "#007AFF",
            paddingVertical: 12,
            paddingHorizontal: 20,
            borderRadius: 8,
            alignItems: "center",
            marginTop: 20,
        },
        buttonText: {
            color: "white",
            fontSize: 16,
            fontWeight: "600",
        },
        verifierSection: {
            backgroundColor: "#f0f0f0",
            padding: 10,
            borderRadius: 8,
            marginBottom: 15,
        },
        label: {
            fontWeight: "bold",
            fontSize: 14,
            textTransform: "uppercase",
            marginBottom: 5,
        },
        verifierText: {
            fontSize: 16,
        },
    });
  2. Open the app/index.tsx file and add the following code under the // Online Presentation - Step 2.2: Handle the 'mdoc-openid4vp://' scheme prefix:

    app/index.tsx
    const handleScanComplete = (scannedValue: string) => {
        setIsScannerVisible(false)
        if (!scannedValue) return
     
        if (scannedValue.startsWith('openid-credential-offer://')) {
            router.replace({
                pathname: '/claim-credential',
                params: { scannedValue }
            })
        } else if (scannedValue.startsWith('mdoc-openid4vp://')) {
            router.replace({
                pathname: '/online-presentation',
                params: { scannedValue }
            })
        }
    }

The application will now redirect to the online-presentation screen whenever a QR code which includes a link prefixed with mdoc-openid4vp:// is scanned. This handles invoking the correct screen in cross-device workflows.

Now, we need to make sure the application navigates to the online-presentation screen when following deep links that are prefixed with mdoc-openid4vp:// as part of same-device workflows.

Add the following code to the WalletProvider component to handle linking:

  1. Open the app/providers/WalletProvider.tsx and add the following code under the // Online Presentation - Step 2.3: Import expo-linking and expo-router to import the required redirect components:
app/providers/WalletProvider.tsx
import * as Linking from 'expo-linking'
import { useRouter } from 'expo-router'
  1. Add the following code under the // Online Presentation - Step 2.4: Initialize router variable comment to use the imported useRouter component:
app/providers/WalletProvider.tsx
const router = useRouter()
  1. Add the following code under the // Online Presentation - Step 2.5: Handle deep link comment to use the router component redirect the user to the online-presentation screen when following a deep link prefixed with mdoc-openid4vp://:
app/providers/WalletProvider.tsx
useEffect(() => {
    if (!wallet) return
 
    const handleDeepLink = (event: { url: string }) => {
        const { url } = event
        console.log('Deep link received:', url)
 
        if (url.startsWith('mdoc-openid4vp://')) {
            router.replace({
                pathname: '/online-presentation',
                params: { scannedValue: url }
            })
        }
    }
 
    Linking.getInitialURL().then((url) => {
        if (url) {
            console.log('Initial URL:', url)
            handleDeepLink({ url })
        }
    })
 
    const subscription = Linking.addEventListener('url', handleDeepLink)
    return () => subscription.remove()
}, [wallet, router])

This function was created in the Claim a credential tutorial to handle QR codes which include OID4VCI credential offers.

  1. Return to the online-presentations.tsx file and add the following code under the // Step 2.6: Create Online Presentation Session comment to create a function that calls the SDK’s createOnlinePresentationSession function with the authorizationRequestURI parameter (the request URI retrieved from the deep link/QR code) to create an OnlinePresentationSession instance and assign it to the session variable:

    app/online-presentation.tsx
    useEffect(() => {
        if (!wallet || !authorisationRequestUri) return
     
        const createSession = async () => {
            try {
                const result = await wallet.credential.mobile.createOnlinePresentationSession({
                    authorisationRequestUri,
                    requireTrustedVerifier: false
                })
     
                if (result.isErr()) {
                    throw new Error('Error creating presentation session')
                }
     
                const session = result.value
     
                setOnlinePresentationSession(session)
     
                if (session.matchedCredentials) {
                    setRequests(session.matchedCredentials)
                }
            } catch (err: any) {
                handleError(err.message)
            }
        }
     
        createSession()
    }, [wallet, authorisationRequestUri, handleError])

Once the result (the response returned by the createOnlinePresentationSession function) is available, your application can access its matchedCredentials object, which contains the list of credentials that match the verifier’s request. Your application will use this information to display the request details and allow the user to select which credentials to share.

We chose to set requireTrustedVerifier parameter to false because we want the SDK to trust all verifiers by default. If you require to interact with a limited list of verifiers, you may want to manually add trusted verifier certificates and set this parameter to true. You can learn more about certificate management in our SDK docs.

  1. Open the app/index.tsx file and add the following code under the {/* Online Presentation - Step 2.7: Add Online Presentation button */} comment to add a button that will open the scanner and enable the user to scan a QR code and start an online presentation session:

    app/index.tsx
    <TouchableOpacity style={styles.button} onPress={() => setIsScannerVisible(true)}>
      <Text style={styles.buttonText}>Online Presentation</Text>
    </TouchableOpacity>
  2. Run the app for your targeted device (using yarn android --device and/or yarn ios --device to rebuild the deleted folders) and perform the following instructions:

    • Use a desktop browser to navigate to the MATTR Labs Maggie’s Groceries demo, where you must provide proof of age for purchasing a restricted item.
    • In the checkout area, use the dropdown list to select Generic Wallet.
    • Select Share from wallet. A QR code will be displayed.
    • Open the tutorial application on your testing device.
    • Select the Online presentation button.
    • Scan the QR code.

You have not yet implemented the logic to handle the presentation request, so the application will not display any information at this stage. Let’s proceed to the next step to fix that.

Handle a presentation request

You will now build the capability to use information returned by the createOnlinePresentationSession function to handle the presentation request. This includes:

  • Displaying what information is requested.
  • Displaying what existing credentials match the requested information.
  • Getting user’s consent to share requested information from matching credentials.

First you will create a component that will display the requested information and allow the user to select which credentials to share.

The following step is also included in the Proximity presentation tutorial. If you had already completed this tutorial and created the RequestCredentialSelector.tsx component you may skip to step 3.2.

  1. In the app/components directory, create a new file named RequestCredentialSelector.tsx and add the following code:

    app/components/RequestCredentialSelector.tsx
    import type { MobileCredentialMetadata, PresentationSessionSuccessRequest } from "@mattrglobal/wallet-sdk-react-native";
    import type React from "react";
    import { FlatList, type ListRenderItem, StyleSheet, Text, TouchableOpacity, View } from "react-native";
     
    type RequestCredentialSelectorProps = {
      requests: PresentationSessionSuccessRequest["request"];
      selectedCredentialIds: string[];
      onToggleSelection: (credentialId: string) => void;
    };
     
    type RequestItem = PresentationSessionSuccessRequest["request"][number];
     
    /**
    * Component that renders a list of credential requests and their matched credentials.
    *
    * @param props - The component props.
    * @param props.requests - The list of credential requests.
    * @param props.selectedCredentialIds - The list of selected credential IDs.
    * @param props.onToggleSelection - Callback function to toggle the selection of a credential.
    * @returns The rendered component.
    */
    export default function RequestCredentialSelector({
      requests,
      selectedCredentialIds,
      onToggleSelection,
    }: RequestCredentialSelectorProps) {
      const renderCredential: ListRenderItem<MobileCredentialMetadata> = ({ item: cred }) => {
        const isSelected = selectedCredentialIds.includes(cred.id);
        return (
          <TouchableOpacity style={styles.credentialItem} onPress={() => onToggleSelection(cred.id)}>
            <View style={styles.selectionIndicator}>{isSelected && <View style={styles.selectionInner} />}</View>
            <Text style={styles.credentialText}>
              {cred.branding?.name ?? "Credential"} ({cred.id})
            </Text>
          </TouchableOpacity>
        );
      };
     
      const renderRequest: ListRenderItem<RequestItem> = ({ item }) => (
        <View style={styles.requestContainer}>
          <Text style={styles.label}>Request Details</Text>
          <Text style={styles.requestInfo}>
            {typeof item.request === "object" ? JSON.stringify(item.request, null, 2) : item.request}
          </Text>
          <Text style={styles.label}>Matched Credentials:</Text>
          <FlatList
            data={item.matchedCredentials}
            keyExtractor={(cred) => cred.id}
            renderItem={renderCredential}
            style={styles.credentialsList}
            contentContainerStyle={styles.credentialsListContent}
          />
        </View>
      );
     
      return (
        <FlatList
          data={requests}
          keyExtractor={(_, idx) => idx.toString()}
          renderItem={renderRequest}
          style={styles.requestsList}
          contentContainerStyle={styles.requestsListContent}
        />
      );
    }
     
    const styles = StyleSheet.create({
      requestsList: {
        flex: 1,
      },
      requestsListContent: {
        paddingBottom: 10,
      },
      requestContainer: {
        backgroundColor: "#f0f0f0",
        padding: 10,
        borderRadius: 8,
        marginBottom: 15,
      },
      requestInfo: {
        fontStyle: "italic",
        fontSize: 12,
        marginBottom: 10,
      },
      label: {
        fontWeight: "bold",
        fontSize: 14,
        textTransform: "uppercase",
        marginBottom: 5,
      },
      credentialsList: {
        maxHeight: 200,
      },
      credentialsListContent: {
        paddingBottom: 10,
      },
      credentialItem: {
        flexDirection: "row",
        alignItems: "center",
        marginBottom: 8,
        paddingVertical: 4,
      },
      selectionIndicator: {
        height: 20,
        width: 20,
        borderRadius: 10,
        borderWidth: 1,
        borderColor: "#000",
        alignItems: "center",
        justifyContent: "center",
        marginRight: 8,
      },
      selectionInner: {
        height: 10,
        width: 10,
        borderRadius: 5,
        backgroundColor: "#000",
      },
      credentialText: {
        fontSize: 16,
      },
    });

    This component displays all existing credentials that match the verification request, and provides a UI for the user to select the credential they wish to share.

    Identifiers of the selected credentials are assigned to the selectedCredentialIds variable, making them available for use in the next steps.

  2. Open the online-presentation.tsx file and add the following code under the // Step 3.2: Import Credential selector component comment to import the RequestCredentialSelector component created in the previous step:

    app/online-presentation.tsx
    import RequestCredentialSelector from '@/components/RequestCredentialSelector'

Before we can display and use the RequestCredentialSelector component, we need to implement a functionality that allows users to select which credentials they want to share.

The handleToggleSelection function updates the selectedCredentialIds state array when users tap on credentials. This function is passed to the RequestCredentialSelector component to handle selection state, and the resulting array of selected credential identifiers will be used when sending the presentation response to the verifier.

  1. Add the following code under the // Step 3.3: Add handleToggleSelection function comment to create the handleToggleSelection function:
app/online-presentation.tsx
const handleToggleSelection = useCallback((id: string) => {
    setSelectedCredentialIds(
        (prev) =>
            prev.includes(id)
                ? prev.filter((item) => item !== id) // Remove if already selected
                : [...prev, id] // Add if not selected
    )
}, [])
  1. Add the following code under the // Step 3.4: Display presentation session details comment to display the request details and the RequestCredentialSelector component, passing in the required props:
app/presentation-request.tsx
  return (
    <View style={styles.container}>
      {/* Display verifier information */}
      <View style={styles.verifierSection}>
        <Text style={styles.label}>Verifier:</Text>
        <Text style={styles.verifierText}>{onlinePresentationSession.verifiedBy.value}</Text>
      </View>
 
      {/* Component to select credentials for the presentation */}
      <RequestCredentialSelector
        requests={requests}
        selectedCredentialIds={selectedCredentialIds}
        onToggleSelection={handleToggleSelection}
      />
 
      {/* Step 4.2: Add Send response button */}
    </View>
  );
  1. Run the app and perform the following instructions:

    • Use a desktop browser to navigate to the MATTR Labs Maggie’s Groceries demo, where you must provide proof of age for purchasing a restricted item.
    • In the checkout area, use the dropdown list to select Generic Wallet.
    • Select Share from wallet. A QR code will be displayed.
    • Open the tutorial application on your testing device.
    • Select the Online presentation button.
    • Scan the QR code.
    • The tutorial application should display the verification request and any matching credentials, enabling the user to select any specific credential for sharing.

The result will look something like this:

Send response

After displaying matching credentials to the user and enabling them to select what credential to share, the last thing you need to do is build the capability to share the selected credential with the verifier.

  1. Add the following code under the // Step 4.1: Add handleSendResponse function comment to create a function that calls the sendResponse method of the SDK’s onlinePresentationSession object and sends the selected credential to the verifier:

    app/online-presentation.tsx
    const handleSendResponse = useCallback(async () => {
        if (!onlinePresentationSession) return
        if (selectedCredentialIds.length === 0) {
            Alert.alert('No Credential Selected', 'Please select at least one credential first.')
            return
        }
     
        try {
            const sendResponseResult = await onlinePresentationSession.sendResponse({
                credentialIds: selectedCredentialIds
            })
     
            if (sendResponseResult.isErr()) {
                throw new Error('Failed to send presentation response')
            }
     
            router.replace('/')
            Alert.alert('Success', 'Presentation response sent successfully!')
        } catch (err: any) {
            handleError(err.message)
            Alert.alert('Error', 'Failed to send presentation response. Terminating session...')
            await onlinePresentationSession.terminateSession()
        }
    }, [onlinePresentationSession, selectedCredentialIds, router, handleError])
  2. Add the following code under the {/* Step 4.2: Add Send response button */} comment to create a button that will enable the user to send a response to the verifier after selecting which credentials to share:

    app/online-presentation.tsx
    <TouchableOpacity style={styles.button} onPress={handleSendResponse}>
      <Text style={styles.buttonText}>Send Response</Text>
    </TouchableOpacity>

Test the application

Let’s test that the application is working as expected in both workflows.

  1. Run the app for your targeted device (using yarn android --device and/or yarn ios --device).
  2. Use a browser on your testing mobile device to navigate to the MATTR Labs Maggie’s Groceries demo, where you must provide proof for purchasing an age restricted item.
  3. In the checkout area, use the dropdown list to select Generic Wallet.
  4. Select Share from wallet.
  5. Select Allow to open the tutorial application.
  6. The tutorial application should be launched on your testing mobile device.
  7. Select the credential you wish to send to the verifier from the list of matched credentials.
  8. Select Send Response.
  9. You should be redirected back to Maggie’s online store and see a Over 21 years old verified indication.
  10. You can now proceed with the interaction (don’t expect any items to be shipped to you by Maggie’s though).

The result will look something like this:

Summary

You have just used the React Native mDoc Holder SDK to build an iOS and Android application that can present a claimed mDoc to a verifier remotely via an online presentation workflow as per ISO/IEC 18013-7:2024 and OID4VP.

Tutorial Workflow

This was achieved by building the following capabilities into the application:

  1. Handle an OID4VP request URI.
  2. Create an online presentation session.
  3. Handle a presentation request.
  4. Send a presentation response.

What’s next?