TutorialsWallet utilitiesProximity presentation

Learn how to build an iOS application that can present an mDoc via a proximity workflow

Introduction

In this tutorial we will use the iOS native mDoc Holder SDK to build an iOS wallet application that can present a claimed mDoc to a verifier that supports proximity verification as per ISO/IEC 18013-5.

Tutorial Workflow

  1. The user launches the wallet application and generates a QR code.
  2. The verifier scans the QR code, connects with the wallet and requests an mDoc for verification.
  3. The wallet displays matching credentials to the user and asks for consent to share them with the verifier.
  4. The verifier receives the wallet’s response and verifies the provided credential.

The result will look something like this:

Prerequisites

Before we get 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 must complete the Claim a credential tutorial, create the application and claim the mDoc provided in the tutorial.
  • As part of your onboarding process you should have been provided with access to the following assets:
    • ZIP file which includes the required framework: (MobileCredentialHolderSDK-*version*.xcframework.zip).
    • Sample Wallet app: You can use this app for reference as we work through this tutorial.

Development environment

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

Testing devices

As this tutorial implements a proximity presentation workflow, you will need two separate physical iOS devices to test the end-to-end result:

  • Wallet device:
    • Supported iOS device to run the built application on, setup with:
      • Biometric authentication.
      • Bluetooth access.
      • Available internet connection.
  • Verifier device:
    • Android/iOS device with an installed verifier application. We recommend downloading and using the MATTR GO Verify example app.
    • Setup with Bluetooth access.

Got everything? Let’s get going!

Presenting a credential for proximity verification

In this tutorial we will build the components that enable a wallet user to present a stored mDoc to a verifier via a proximity presentation workflow.

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

  1. Create a QR code for the verifier to scan and establish a secure connection.
  2. Receive and handle a presentation request from the verifier.
  3. Send a matching mDoc presentation to the verifier.

Create a new application view

To avoid confusion with the issuance workflow, we will create a new view for the application you build in the Claim a credential tutorial. You may choose to implement a different architecture in your production apps.

  1. Create a swift file named PresentCredentialsView.swift and add the following code into the new file:

    Swift
    import SwiftUI
    import MobileCredentialHolderSDK
     
    struct PresentCredentialsView: View {
    		// Step 3.1: Add selectedCredential var
        @ObservedObject var viewModel: PresentCredentialsViewModel
        init(mobileCredentialHolder: MobileCredentialHolder) {
            self.viewModel = PresentCredentialsViewModel(mobileCredentialHolder: mobileCredentialHolder)
        }
     
        var body: some View {
            VStack {
    		        // Step 2.3: Add button to generate QR code
                EmptyView()
                // Step 2.5: Display QR Code for scanning
                // Step 3.3: Display matched credentials for user consent
                Spacer()
            }
        }
     
       // Step 2.4: Create QR code view and generateQR Code function
    }
     
    class PresentCredentialsViewModel: ObservableObject {
    		// Step 2.1: Create qrCode and presentationSession variables
    		// Step 3.2: Add requestedDocuments var
        var mobileCredentialHolder: MobileCredentialHolder
        init(mobileCredentialHolder: MobileCredentialHolder) {
            self.mobileCredentialHolder = mobileCredentialHolder
        }
     
        // Step 2.2: Create proximity presentation session and generate QR code
     
        // Step 4.1: Send Response function
    }

This will serve as the basic structure for your application. We will copy and paste different code snippets into specific locations in this codebase to achieve the different functionalities. These locations are indicated by comments that reference both the section and the step.

  1. Add the following code in your ContentView file under the // Online Presentation Step 1.2: Add button to navigate to present credential view comment to add a new Present Credentials button that will navigate the user to the new PresentCredentialsView:

We recommend copying and pasting the comment text (e.g. // Online Presentation Step 1.2: Add button to navigate to present credential view) to easily locate it in the code.

Swift
NavigationLink(destination: PresentCredentialsView) {
    Text("Present Credentials")
}.padding()
  1. Add the following code in your ContentView file under the // Online Presentation Step 1.3: Create PresentCredentialsView comment to create the new view the button from the previous step will navigate to:

    Swift
    var presentCredentialsView: some View {
      PresentCredentialsView(
          mobileCredentialHolder: viewModel.mobileCredentialHolder
      )
    }
  2. Run the app and select the Present Credentials button. You should see a result similar to the following:

As the user selects the Present Credentials button, they are navigated to the new PresentCredentialsView (which is still empty at the moment, but we’ll take care of that in the next step).

Create a QR code for the verifier to scan

Tutorial Workflow

Now that we have created the new view, the first capability we need to build in it is to establish a secure communication channel between the verifier and holder devices. As defined in ISO 18130-5, a proximity presentation workflow is always initiated by the holder (wallet user), who must create a QR code for the verifier to scan in order to initiate the device engagement phase.

To achieve this, your wallet application needs a UI element for the user to interact with and trigger device engagement by calling the createProximityPresentationSession function.

💡 From this step onwards we will be working solely in the PresentCredentialsView.swift file.

  1. Add the following code under the // Step 2.1: Create qrCode and presentationSession variables comment to create new variables to hold the QR code and presentation session.

    Swift
        @Published var qrCode: String?
        @Published var presentationSession: ProximityPresentationSession?
  2. Add the following code under the // Step 2.2: Create proximity presentation session and generate QR code comment to call the ProximityPresentationSession function when the user selects the Create QR Code button:

    Swift
        func createQRCode() {
            Task { @MainActor in
                do {
                    presentationSession = try await mobileCredentialHolder.createProximityPresentationSession(
                        onRequestReceived: onRequestReceived(_:error:)
                    )
                    qrCode = presentationSession?.deviceEngagement
     
                } catch {
                    print(error)
                }
            }
        }
     
        private func onRequestReceived(_ mobileCredentialRequests: [(request: MobileCredentialRequest,
                                                                     matchedMobileCredentials: [MobileCredentialMetadata])]?, error: Error?) {
           // Step 3.4: display matched credentials
        }
  3. Add the following code under the // Step 2.3: Add button to generate QR code comment to add a button that will generate the QR code when the user selects it:

    Swift
        Button {
            viewModel.createQRCode()
        } label: {
            Text("Create QR code")
        }

Now, when the user selects the Create QR Code button, the application will call the ProximityPresentationSession function, which returns a ProximityPresentationSession instance that includes a deviceEngagement string in base64 format:

"mdoc:owBjMS4wAYIB2BhYS6QBAiABIVgghaBYJe7KSqcEolhmnIJaYJ2AIevkKbEy5xP7tkwlqAwiWCAMGCGe6uFI2hKeghb59h_K4hPV-Ldq6vnaxsRiySMH9gKBgwIBowD0AfULUKRoj0ZH60Qco-m0k97qRSQ"

The deviceEngagement string is always prefixed with mdoc: and contains the information required to establish a secure connection between the two devices, including:

  • Wireless communication protocols supported by the holder.
  • How the verifier can connect with the holder.
  • Ephemeral public key which is unique to the current session.
  • Additional feature negotiations.

Your app needs to convert this deviceEngagement string into a QR code and display it in the wallet UI for the verifier to scan.

  1. Add the following code under the // Step 2.4: Create QR code view and generateQR Code function comment to create a new view to display the QR code in, as well as call the function that takes the data from the deviceEngagement string and converts it into a QR code:

    Swift
    var qrCodeView: some View {
        ZStack {
            if let imageData =  generateQRCode(data: viewModel.qrCode?.data(using: .utf8) ?? Data()),
               let image = UIImage(data: imageData) {
                Image(uiImage: image)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
            }
        }
    }
     
    func generateQRCode(data: Data) -> Data? {
        guard let filter = CIFilter(name: "CIQRCodeGenerator") else { return nil }
        filter.setValue(data, forKey: "inputMessage")
        guard let ciimage = filter.outputImage else { return nil }
        let transform = CGAffineTransform(scaleX: 10, y: 10)
        let scaledCIImage = ciimage.transformed(by: transform)
        let uiimage = UIImage(ciImage: scaledCIImage)
        return uiimage.pngData()
    }
  2. Add the following code under the // Step 2.5: Display QR Code for scanning comment to display the QR code in the new QR code view once it is available:

    Swift
    if viewModel.qrCode != nil {
        qrCodeView
            .frame(maxWidth: 300, maxHeight: 300)
            .padding(30)
        Spacer()
    }
  3. Run the app, select the Present Credentials button and then select the Create QR code button. You should see a result similar to the following:

As the user selects the Present Credentials button, they are navigated to the new PresentCredentialsView where they see the Create QR code button. When they select the Create QR code button, the wallet generates and displays a QR code that can be scanned by a verifier device to establish a secure proximity communication channel over Bluetooth.

Once the QR code is displayed, the ProximityPresentationSession function enters a listening state, ready to establish a Bluetooth connection with a verifier app that scans this QR Code.

Tutorial Workflow

Once the verifier scans the QR code, the devices will automatically exchange public keys to establish a secure communication channel, enabling the verifier to send a presentation request, which details what information is required and for what purpose.

Handle the presentation request

Tutorial Workflow

The createProximityPresentationSession function can handle three types of events:

  • onConnected: When a secure connection is established.
  • onSessionTerminated: When a secure connection is terminated for whatever reason.
  • onRequestReceived: When a presentation request is received from the verifier.

onConnected and onSessionTerminated are optional events and will not be implemented in this tutorial. Check out our SDK Docs for a complete description of these events and how you can handle them.

When the wallet SDK receives a presentation request from a verifier, an onRequestReceived event is triggered. The wallet then checks its credential storage for any credentials that match the information defined in this request.

The wallet then needs to present these matching credentials to the user, and provide a UI element to provide consent to sharing this information with the verifier.

  1. Add the following code under the // Step 3.1: Add selectedCredential var comment to create a new variable in the PresentCredentialsView view that will hold credentials that match the presentation request:

    Swift
    @State var selectedCredential: String?
  2. Add the following code under the // Step 3.2: Add requestedDocuments var comment create a new variable that will hold the credentials that were requested by the verifier:

    Swift
    @Published var requestedDocuments: [(request: MobileCredentialRequest,
                                         matchedMobileCredentials: [MobileCredentialMetadata])]? = nil
  3. Add the following code under the // Step 3.3: Display matched credentials for user consent comment to create the logic to display matching credentials to the user and provide a UI element for them to consent sharing these credentials with the verifier:

    Swift
    if viewModel.requestedDocuments != nil {
        let matchedCredentials = viewModel.requestedDocuments![0].matchedMobileCredentials
        List(selection: $selectedCredential) {
            Section(header: Text("Requested Document")) {
                Text("\(viewModel.requestedDocuments![0].request.docType)")
            }
            Section(header: Text("Please select matching document to present")) {
                ForEach(matchedCredentials, id: \.id) { credential in
                    Text(credential.docType)
                }
            }
        }
        if let selectedCredential {
            Button {
                    // Step 4.2: call sendResponse
            } label: {
                Text("Send Response")
            }
        }
    }
  4. Add the following code under the // Step 3.4: Display matched credentials comment to create the logic that displays matching credentials to the user when a presentation request is received:

    Swift
    DispatchQueue.main.async {
        self.requestedDocuments = mobileCredentialRequests
    }
  5. Run the app, select the Present Credentials button and then select the Create QR code button. Next, use your testing verifier app to scan the presented QR code and send a presentation request. You should see a result similar to the following:

As the user selects the Present Credentials button, they are navigated to the new PresentCredentialsView where they see the Create QR code button. When they select the Create QR code button, the wallet generates and displays a QR code.

When a compliant verifier app scans the QR code, a secure communication channel is established via Bluetooth. The verifier then sends a presentation request, which is displayed to the user on their digital wallet, alongside any credentials they have stored in their wallet that match the request.

Send a presentation response

Tutorial Workflow

The next (and last!) capability we need to build is for the wallet to sent a presentation response upon receiving consent from the holder to share information with the verifier.

Once the user provides this consent by selecting the Send Response button, the wallet application should call the sendResponse function to share the selected credentials with the verifier as a presentation response.

  1. Add the following code under the // Step 4.1: Send response function comment to define the sendResponse function:

    Swift
    func sendResponse(_ id: String) {
        Task { @MainActor in
            do {
                try await presentationSession?.sendResponse(credentialIds: [id], terminateSession: true)
            } catch {
                print(error)
            }
        }
    }
  2. Add the following code under the // Step 4.2: call sendResponse comment to create the logic to call the sendResponse function once the user selects the Send Response button:

    Swift
    viewModel.sendResponse(selectedCredential)

    The sendResponse function signs the presentation response with the user’s device private key (to prove Device authentication) and shares it as an encoded CBOR file.

  3. Run the app and perfrm the following:

    1. Select the Present Credentials button.
    2. Select the Create QR code button.
    3. Use your testing verifier app to scan the present QR code and send a presentation request.
    4. Back on the holder device, select the matching credential to share and select the Send Response button.

    You should see a result similar to the following:

As the user selects the credential to share, the verifier app will receive the presentation response, verify any incoming credentials and display the verification results.

Congratulations, you have now completed this tutorial, and should have a working wallet application that can claim an mDoc using an OID4VCI workflow, and present it to a verifier for proximity verification via Bluetooth.

Summary

You have just used the iOS mDoc holder SDK to built a wallet application that can present a claimed mDoc to a verifier that supports proximity verification as per ISO/IEC 18013-5.

Tutorial Workflow

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

  • Generating a QR code for the verifier to scan and establish a secure communication channel.
  • Receive and handle a presentation request from the verifier.
  • Display matching credentials to the user and ask for consent to share them with the verifier.
  • Send matching credentials to the verifier as a presentation response.

What’s next?

  • You can check out the iOS mDoc holder SDK Docs to learn more about available functions and classes.