light-mode-image
Learn
Mobile

Learn how to build an application that can verify an mDoc from another app on the same device

Overview

In this tutorial you will use the mDocs Mobile Verifier SDKs to build an application that can verify an mDoc presented from a different application on the same device via OID4VP, as defined in ISO 18013-7 Annex B.

Remote mobile app verification overview

  1. A relying party uses the Mobile Verifier SDK to embed a remote verification workflow into a mobile application.
  2. When a user interacts with the mobile application, a matching wallet application installed on their mobile device is invoked to request an mDoc for verification.
  3. The user consents to sharing the requested information.
  4. The user's wallet application shares the matching mDoc with the MATTR VII tenant configured by the Mobile Verifier SDK to perform the verification workflow.
  5. The MATTR VII tenant performs the required checks and returns the verification results via the Mobile Verifier SDK to the verifier application.
  6. The user journey continues based on the verification results.

The result will look something like this:

Coming soon...

Coming soon...

To achieve this, you will build the following capabilities into your verifier application:

  • Initialize the SDK, so that your application can use its functions and classes.
  • Request an mDoc for verification from a compliant wallet application.
  • Handle the redirect from the wallet application.
  • Display the verification results.

Prerequisites

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

Prior knowledge

  • The remote verification workflow described in this tutorial is based on the OID4VP specification and the ISO 18013-7 standard. If you are unfamiliar with these, refer to the following resources for more information:

  • We assume you have experience developing applications in the relevant programming languages and frameworks (Swift for iOS and Kotlin for Android).

Assets

  • Complete the sign up form to get trial access to MATTR VII and the MATTR Portal, and then Create a tenant.
  • As part of your MATTR Pi SDK onboarding process you should have been provided with access to the following SDK resources:
    • ZIP file which includes the required framework: (MobileCredentialVerifierSDK-*version*.xcframework.zip).
    • Sample Verifier app: You can use this app for reference as you work through this tutorial.

This tutorial is only meant to be used with the most recent version of the iOS mDocs Verifier SDK.

Coming soon...

Coming soon...

Development environment

  • Xcode setup with either:
    • Local build settings if you are developing locally.
    • iOS developer account if you intend to publish your app.

Coming soon...

Coming soon...

Testing device

You will need to have a mobile device to test the workflow.

Supported iOS device to run the built Verifier application on, setup with:

  • Available internet connection
  • A wallet application that can present an mDoc remotely. If you don't have one available, we recommend using the app built in our Holder SDK remote presentation tutorial.
  • Use your testing wallet application to claim an mDoc by scanning the following QR code:

QR code

Coming soon...

Coming soon...

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 verifier application.
  2. The verifier application uses the embedded Mobile Verifier 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 wallet application.
  4. The verifier application uses the link to invoke a matching wallet application using a redirect.
  5. The wallet 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 wallet application.
  7. The wallet 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 verifier application.
  9. The verifier application surfaces the verification results to the user and the interaction continues.

You will build this workflow in two parts:

  1. Part 1: Setup the MATTR VII Verifier tenant.
  2. Part 2: Build a mobile application with mDocs verification capabilities.

Part 1: Setup the MATTR VII Verifier tenant

The MATTR VII tenant will be used to interact with your mobile application (generating a verification request) and the wallet application (presenting an mDoc for verification) as per OID4VP and ISO/IEC 18013-7 Annex B. 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 a remote 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 MATTR 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 mobile application.

  1. In the navigation panel on the left-hand side, expand the Credential Verification menu.
  2. Select Applications.
  3. Select Create new.
  4. Enter a meaningful Name for your application (e.g. "My Mobile Verifier Application").
  5. Use the Type dropdown to select iOS as you are building an iOS application.
  6. Enter your Apple Developer Team ID in the Team ID field. You can find it in the Membership details section of your Apple Developer account.
  7. Enter your iOS application bundle identifier in the Bundle ID field. This is the unique identifier of your iOS application, which you can set in your Xcode project settings. This will be used by the MATTR VII tenant to validate incoming requests are from a known and trusted application.
  8. In the OpenID4VP Configuration section, enter your redirect URI in the Redirect URI field. This should be structured as follows: {your_app_bundleId}://my/path. This is the URI the user is redirected to after presenting the credential from their wallet. This can point to your app via a custom URI scheme or a web page.
  9. Select Create.

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

Request
POST /v2/presentations/applications
Request body
{
  "name": "My Mobile Verifier Application",
  "type": "ios",
  "teamId": "A2B3C4D5E6",
  "bundleId": "io.mattrlabs.dev.sampleApp.MdocSampleApp",
  "openid4vpConfiguration": {
    "redirectUri": "io.mattrlabs.dev.sampleApp.MdocSampleApp://my/path"
  },
  "resultAvailableInFrontChannel": true
}
  • name : You can use whatever name you'd like, as long as it is unique on your tenant.
  • type : Use ios as you are building an iOS application.
  • teamId : Replace with your Apple Developer Team ID associated with your iOS application. You can find it in the Membership details section of your Apple Developer account.
  • bundleId : Replace with your iOS application bundle identifier. This is the unique identifier of your iOS application, which you can set in your Xcode project settings. This will be used by the MATTR VII tenant to validate incoming requests are from a known and trusted application.
  • openid4vpConfiguration:
    • redirectUri : This is the URI the user is redirected to after presenting the credential from their wallet. This can point to your app via a custom URI scheme or a web page. Make sure to use your actual app bundle ID, so that the redirect URI is structured as follows: {your_app_bundleId}://my/path.
  • resultAvailableInFrontChannel : Setting this to true makes the verification results available directly to the mobile application.

Response

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

Create a supported wallet configuration

Verifier 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. In the navigation panel on the left-hand side, expand the Credential Verification menu.
  2. Click on Supported wallets.
  3. Click on Create new.
  4. Enter a meaningful Name for the new supported wallet (e.g. "My Supported Wallet").
  5. Enter mdoc-openid4vp:// in the Authorization Endpoint field. 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. Click on Create.

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.

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": "My Trusted Wallet Provider",
  "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": "My Trusted Wallet Provider",
  "openid4vpConfiguration": {
    "authorizationEndpoint": "mdoc-openid4vp://"
  }
}
  • id : You will use this value later to indicate this is the wallet the verifier application expects to receive mDocs from.

Configure a trusted issuer

  1. In the navigation panel on the left-hand side, expand the Credential Verification menu.
  2. Click on Trusted issuers.
  3. Click on Create new.
  4. Copy and paste the following certificate in the Certificate PEM file field:
-----BEGIN CERTIFICATE-----
MIICYzCCAgmgAwIBAgIKXhjLoCkLWBxREDAKBggqhkjOPQQDAjA4MQswCQYDVQQG
EwJBVTEpMCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0Ew
HhcNMjQwMTE4MjMxNDE4WhcNMzQwMTE1MjMxNDE4WjA4MQswCQYDVQQGEwJBVTEp
MCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0EwWTATBgcq
hkjOPQIBBggqhkjOPQMBBwNCAASBnqobOh8baMW7mpSZaQMawj6wgM5e5nPd6HXp
dB8eUVPlCMKribQ7XiiLU96rib/yQLH2k1CUeZmEjxoEi42xo4H6MIH3MBIGA1Ud
EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRFZwEOI9yq
232NG+OzNQzFKa/LxDAuBgNVHRIEJzAlhiNodHRwczovL21vbnRjbGlmZi1kbXYu
bWF0dHJsYWJzLmNvbTCBgQYDVR0fBHoweDB2oHSgcoZwaHR0cHM6Ly9tb250Y2xp
ZmYtZG12LnZpaS5hdTAxLm1hdHRyLmdsb2JhbC92Mi9jcmVkZW50aWFscy9tb2Jp
bGUvaWFjYXMvMjk0YmExYmMtOTFhMS00MjJmLThhMTctY2IwODU0NWY0ODYwL2Ny
bDAKBggqhkjOPQQDAgNIADBFAiAlZYQP95lGzVJfCykhcpCzpQ2LWE/AbjTGkcGI
SNsu7gIhAJfP54a2hXz4YiQN4qJERlORjyL1Ru9M0/dtQppohFm6
-----END CERTIFICATE-----
  1. Click on Add.

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.

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.

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 mobile 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 mobile verifier application:

  1. Setup your environment: Setup the required infrastructure for your mobile application.
  2. Initialize the SDK: So that the SDK functions are available in your mobile application.
  3. Request a credential from wallet application: Build the capability to request an mDoc for verification from a wallet application.
  4. Display verification results:

Step 1: Environment setup

Step 1: Create a new project

Follow the detailed instructions to Create a new Xcode Project and add your organization's identifier.

Step 2: Unzip the dependencies file

  1. Unzip the MobileCredentialVerifierSDK-*version*.xcframework.zip file.
  2. Drag the MobileCredentialVerifierSDK-*version*.xcframework folder into your project.
  3. Configure MobileCredentialVerifierSDK.xcframework to Embed & sign.

See Add existing files and folders for detailed instructions.

This should result in the the following framework being added to your project:

Framework added

Step 3: Run the application

Select Run and make sure the application launches with a “Hello, world!” text in the middle of the display, as shown in the following image:

Application ready

Coming soon...

Coming soon...

Step 2: Initialize the SDK

The first capability you will build into your app is to initialize the SDK so that the app can use its functions and classes. To achieve this, you need to import the MobilecredentialVerifierSDK framework and then initialize the MobileCredentialVerifier class.

  1. Open the ContentView file in your new project and replace any existing code with the following:

    ContentView
    import SwiftUI
    internal import Combine
    // Step 2.3: Import MobileCredentialVerifierSDK
    
    struct ContentView: View {
        @ObservedObject var viewModel: VerifierViewModel = VerifierViewModel()
    
        var body: some View {
            NavigationStack(path: $viewModel.navigationPath) {
                VStack {
                    Button("Request credentials") {
                        viewModel.requestCredentials()
                    }
                    .padding()
                }
                .navigationDestination(for: NavigationState.self) { destination in
                    switch destination {
                    case .viewResponse:
                        presentationResponseView
                    }
                }
            }
            // Step 4.2: Handle MATTR VII redirect
    
        }
    
        // MARK: Verification Views
    
        var presentationResponseView: some View {
            // Step 4.4: Create PresentationResponseView
            EmptyView()
        }
    }
    
    // MARK: VerifierViewModel
    
    final class VerifierViewModel: ObservableObject {
        @Published var navigationPath = NavigationPath()
        // Step 2.4: Setup platform configuration
    
        // Step 2.5: Add MobileCredentialVerifier var
    
        // Step 2.6: Initialize the SDK
    
        // Step 3.1: Create MobileCredentialRequest instance
    
        // Step 3.2: Create receivedDocuments variable
    
    }
    
    // MARK: Same Device Verification
    
    extension VerifierViewModel {
        func requestCredentials() {
        // Step 3.3: Request credentials
        }
    }
    
    // MARK: - Navigation
    enum NavigationState: Hashable {
        case viewResponse
    }

    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 both the section and the step.

    We recommend copying and pasting the comment text in Xcode search field (e.g. // Step 2.3: Import MobileCredentialVerifierSDK) to easily locate it in the code.

  2. Create a new file named Constants and paste the following code into it to define constants which are required to initialize the SDK:

    Constants
    import Foundation
    
    enum Constants {
        static let tenantHost = URL(string: "https://learn.vii.au01.mattr.global")!
        static let applicationID = "74f91a0f-5909-43b0-a431-6da2397d1f86"
    }
  3. Return to the ContentView file and add the following code after the // Step 2.3: Import MobileCredentialVerifierSDK comment to import MobileCredentialVerifierSDK and gain access to the SDK capabilities:

    ContentView
    import MobileCredentialVerifierSDK
  4. Add the following code under the // Step 2.4: Setup platform configuration comment to create a PlatformConfiguration instance:

    ContentView
        let platformConfiguration = PlatformConfiguration(
            tenantHost: Constants.tenantHost
        )

    This instance configures the MATTR VII tenant that the SDK will interact with (tenantHost) based on the constants defined in the Constants file.

  5. Add the following code after the // Step 2.5: Add MobileCredentialVerifier var comment to create a variable that will hold the mobileCredentialVerifier instance when the SDK is initialized:

    ContentView
        var mobileCredentialVerifier: MobileCredentialVerifier
  6. Add the following code after the // Step 2.6: Initialize the SDK comment to initialize the MobileCredentialVerifier instance with the parameters defined in the platformConfiguration instance:

    ContentView
        init() {
            do {
                mobileCredentialVerifier = MobileCredentialVerifier.shared
                try mobileCredentialVerifier.initialize(platformConfiguration: platformConfiguration)
            } catch {
                print(error.localizedDescription)
            }
        }
  7. Run the app to ensure it compiles successfully.

Coming soon...

Coming soon...

Step 3: Request a credential from wallet application

Once the SDK is initialized, you can start building the capabilities to request an mDoc for verification from a wallet application. This is done by:

  1. Creating a request object that defines what information is required for verification.
  2. Sending this request to the wallet application installed on the device.
  3. Redirecting the user to the wallet application to present the requested mDoc.
  1. Open the ContentView file and add the following code under the // Step 3.1: Create MobileCredentialRequest instance comment to create a new MobileCredentialRequest instance:

    ContentView
        let mobileCredentialRequest = MobileCredentialRequest(
            docType: "org.iso.18013.5.1.mDL",
            namespaces: [
                "org.iso.18013.5.1":  [
                    "family_name": false,
                    "given_name": false,
                    "birth_date": false
                ]
            ]
        )

    This object defines what information is required for verification:

    • The requested credential type (e.g. org.iso.18013.5.1.mDL).
    • The claims required for verification (e.g. family_name).
    • The requested namespace (e.g. org.iso.18013.5.1).
    • Whether or not the verifier intends to persist the claim value (true/false).

    For the verification to be successful, the presented credential must include the referenced claim against the specific namespace defined in the request. Our example requests the birth_date under the org.iso.18013.5.1 namespace. If a wallet responds to this request with a credential that includes a birth_date but rather under the org.iso.18013.5.1.US namespace, the claim will not be verified.

  2. Add the following code under the Step 3.2: Create receivedDocuments variable comment to create a new receivedDocuments variable that will hold the response received from the wallet application:

    ContentView
        @Published var receivedDocuments: [MobileCredentialPresentation] = []
  3. Add the following code under the // Step 3.3: Request credentials comment to call the SDK's requestMobileCredentials method:

    ContentView
            Task { @MainActor in
                // Clean the response before fetching a new one
                receivedDocuments = []
                do {
                    let onlinePresentationResult = try await mobileCredentialVerifier.requestMobileCredentials(request: [mobileCredentialRequest], challenge: UUID().uuidString, applicationId: Constants.applicationID)
                    guard let receivedCredentials = onlinePresentationResult.mobileCredentialResponse?.credentials else {
                        let errorMessage = onlinePresentationResult.error?.message ?? "No error message"
                        print("No response received: \(errorMessage)")
                        return
                    }
                    receivedDocuments = receivedCredentials
                } catch {
                    print(error.localizedDescription)
                }
            }

    The following parameters are passed to the requestMobileCredentials method:

    • request : Defines what information to request. This example is passing the mobileCredentialRequest instance you created in the previous step.
    • challenge : Unique challenge generated by the SDK to identify this specific presentation session. Should be a unique, unpredictable value generated for each verification session to mitigate replay attacks by ensuring the response from the wallet app is tied to the current request and cannot be reused maliciously. Always generate a new challenge for every same credential request.
    • applicationId : Identifier of the application that will be used to verify the request. In this example it is retrieved from the information defined in the Constants file.
  4. Run the app and press Request credentials button.

    You will be redirected to a compliant wallet application, where you will see the verification request details and choose what mDoc to present for verification.

Once you send the response from the wallet nothing will happen, which is expected at this stage. In the next step you will build the capability to redirect the user back to the verifier application and handle the response from the wallet.

Coming soon...

Coming soon...

Step 4: Display verification results

Once the user provides their consent to share the requested information, the wallet application will send the response back to the MATTR VII tenant, which will then return the verification results to your verifier application and redirect the user back to the configured redirect URI. In this part of the tutorial you will build the capability to handle this redirect and display the verification results in your application.

To enable the redirect back to your verifier application you must register a redirection link. This could be either a Universal link or a custom URL scheme. In this tutorial you will use a custom URL scheme.

  1. Register a custom URL scheme in your verifier application:

    • Open the project view and select your application target.
    • Select the Info tab.
    • Scroll down and expand the URL Types area.
    • Select the plus button.
    • Insert io.mattrlabs.dev.sampleApp.MdocSampleApp in both the Identifier and URL Schemes fields.

Custom URL registration

If you used a different bundle identifier when you configured the MATTR VII verifier application, make sure to replace io.mattrlabs.dev.sampleApp.MdocSampleApp with the bundle identifier you used.

  1. In your ContentView file, add the following code under the // Step 4.2: Handle MATTR VII redirect comment to handle the redirect from the wallet application:

    ContentView
            .onOpenURL { url in
                // Navigate to response screen
                viewModel.navigationPath.append(NavigationState.viewResponse)
                viewModel.mobileCredentialVerifier.handleDeepLink(url)
            }

    This will pass the redirect URL to the SDK's handleDeepLink method, which will process the response from the wallet application and update the receivedDocuments variable with the verification results.

  2. Create a new file named DocumentView and add the following code to display the retrieved verification results:

    DocumentView
    import MobileCredentialVerifierSDK
    import SwiftUI
    
        struct DocumentView: View {
    
            var viewModel: DocumentViewModel
    
            var body: some View {
                VStack(alignment: .leading, spacing: 10) {
                    Text(viewModel.docType)
                        .font(.title)
                        .fontWeight(.bold)
                        .padding(.bottom, 5)
    
                    Text(viewModel.verificationResult)
                        .font(.title)
                        .fontWeight(.bold)
                        .foregroundStyle(viewModel.verificationFailedReason == nil ? .green : .red)
                        .padding(.bottom, 5)
    
                    if let verificationFailedReason = viewModel.verificationFailedReason {
                        Text(verificationFailedReason)
                            .font(.title3)
                            .fontWeight(.bold)
                            .foregroundStyle(.red)
                            .padding(.bottom, 5)
                    }
    
                    ForEach(viewModel.namespacesAndClaims.keys.sorted(), id: \.self) { key in
                        VStack(alignment: .leading, spacing: 5) {
                            Text(key)
                                .font(.headline)
                                .padding(.vertical, 5)
                                .padding(.horizontal, 10)
                                .background(Color.gray.opacity(0.2))
                                .cornerRadius(5)
    
                            ForEach(viewModel.namespacesAndClaims[key]!.keys.sorted(), id: \.self) { claim in
                                HStack {
                                    Text(claim)
                                        .fontWeight(.semibold)
                                    Spacer()
                                    Text(viewModel.namespacesAndClaims[key]![claim]! ?? "")
                                        .fontWeight(.regular)
                                }
                                .padding(.vertical, 5)
                                .padding(.horizontal, 10)
                                .background(Color.white)
                                .cornerRadius(5)
                                .shadow(radius: 1)
                            }
                        }
                        .padding(.vertical, 5)
                    }
    
                    if !viewModel.claimErrors.isEmpty {
                    Text("Failed Claims:")
                        .font(.headline)
                        .padding(.vertical, 5)
    
                        ForEach(viewModel.claimErrors.keys.sorted(), id: \.self) { key in
                            VStack(alignment: .leading, spacing: 5) {
                                Text(key)
                                    .font(.headline)
                                    .padding(.vertical, 5)
                                    .padding(.horizontal, 10)
                                    .background(Color.gray.opacity(0.2))
                                    .cornerRadius(5)
    
                                ForEach(viewModel.claimErrors[key]!.keys.sorted(), id: \.self) { claim in
                                    HStack {
                                        Text(claim)
                                            .fontWeight(.semibold)
                                        Spacer()
                                        Text(viewModel.claimErrors[key]![claim]! ?? "")
                                            .fontWeight(.regular)
                                    }
                                    .padding(.vertical, 5)
                                    .padding(.horizontal, 10)
                                    .background(Color.white)
                                    .cornerRadius(5)
                                    .shadow(radius: 1)
                                }
                            }
                            .padding(.vertical, 5)
                        }
                    }
                }
                .padding()
                .background(RoundedRectangle(cornerRadius: 10).fill(Color.white).shadow(radius: 5))
                .padding(.horizontal)
            }
        }
    
        // MARK: DocumentViewModel
    
        class DocumentViewModel: ObservableObject {
            let docType: String
            let namespacesAndClaims: [String: [String: String?]]
            let claimErrors: [String: [String: String?]]
            let verificationResult: String
            let verificationFailedReason: String?
    
            init(from presentation: MobileCredentialPresentation) {
                self.docType = presentation.docType
                self.verificationResult = presentation.verificationResult.verified ? "Verified" : "Invalid"
                self.verificationFailedReason = presentation.verificationResult.reason?.message
    
                self.namespacesAndClaims = presentation.claims?.reduce(into: [String: [String: String]]()) { result, outerElement in
                    let (outerKey, innerDict) = outerElement
                    result[outerKey] = innerDict.mapValues { $0.textRepresentation }
                } ?? [:]
    
                self.claimErrors = presentation.claimErrors?.reduce(into: [String: [String: String]]()) { result, outerElement in
                    let (outerKey, innerDict) = outerElement
                    result[outerKey] = innerDict.mapValues { "\($0)" }
                } ?? [:]
            }
        }
    
        // MARK: Helper
        extension MobileCredentialElementValue {
            var textRepresentation: String {
                switch self {
                case .bool(let bool):
                    return "\(bool)"
                case .string(let string):
                    return string
                case .int(let int):
                    return "\(int)"
                case .unsigned(let uInt):
                    return "\(uInt)"
                case .float(let float):
                    return "\(float)"
                case .double(let double):
                    return "\(double)"
                case let .date(date):
                    let dateFormatter = DateFormatter()
                    dateFormatter.dateStyle = .short
                    dateFormatter.timeStyle = .none
                    return dateFormatter.string(from: date)
                case let .dateTime(date):
                    let dateFormatter = DateFormatter()
                    dateFormatter.dateStyle = .short
                    dateFormatter.timeStyle = .short
                    return dateFormatter.string(from: date)
                case .data(let data):
                    return "Data \(data.count) bytes"
                case .map(let dictionary):
                    let result = dictionary.mapValues { value in
                        value.textRepresentation
                    }
                    return "\(result)"
                case .array(let array):
                    return array.reduce("") { partialResult, element in
                        partialResult + element.textRepresentation
                    }
                    .appending("")
                @unknown default:
                    return "Unknown type"
                }
            }
        }

    The DocumentView file comprises the following elements:

    • DocumentView : Basic UI layout for viewing received documents and verification results.
    • DocumentViewModel : This class takes MobileCredentialPresentation and converts its elements into strings that are displayed in the DocumentView.
    • Extension of MobileCredentialElementValue which converts the values of received claims into a human-readable format.
  3. Return to the ContentView file and replace the EmptyView() under the // Step 4.4: Create PresentationResponseView comment with the following code to display the DocumentView view when verification results are available:

    ContentView
            ZStack {
            if viewModel.receivedDocuments.isEmpty {
                VStack(spacing: 40) {
                    Text("Waiting for response...")
                        .font(.title)
                    ProgressView()
                        .progressViewStyle(.circular)
                        .scaleEffect(2)
                }
            } else {
                ScrollView {
                    ForEach(viewModel.receivedDocuments, id: \.docType) { doc in
                        DocumentView(viewModel: DocumentViewModel(from: doc))
                            .padding(10)
                    }
                }
            }
        }

Coming soon...

Coming soon...

Test the end-to-end workflow

  1. Run the app.
  2. Select the Request credentials button.
    You should be redirected to a compliant wallet application, where you will see the verification request details and choose what mDoc to present for verification.
  3. Use the wallet application to present the requested mDoc.
    You will be redirected back to the verifier application where you will see the verification results.

You should see a result similar to the following:

Coming soon...

Coming soon...

  1. The verifier app starts a presentation session and gets redirected.
  2. The user is redirected to a compliant wallet application.
  3. The user provides their consent to share the requested information.
  4. The wallet application sends the response back to the MATTR VII tenant.
  5. The MATTR VII tenant redirects the user back to the verifier app with the verification results.
  6. The verifier app fetches the result and presents the result to user.

Congratulations! Your verifier application can now verify mDocs presented from a compliant wallet installed on the same mobile device.

Summary

You have just used the mDocs Mobile Verifier SDKs to build an application that can verify an mDoc presented from a compliant wallet on the same device using a remote presentation workflow as per OID4VP and ISO/IEC 18013-7 Annex B.

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

  • Initialize the SDK, so that your application can use its functions and classes.
  • Request an mDoc for verification from a compliant wallet application.
  • Display verification results in your verifier application.

What's next?

  • You can check out the SDKs reference documentation to learn more about available functions and classes:

How would you rate this page?