GuidesiOS mDocs Holder SDK๐ŸŽ“ Claim a credential

Learn how to build an iOS application that can claim an mDoc via OID4VCI

Introduction

In this tutorial we will use the iOS mDoc holder SDK to build an iOS application that can claim an mDoc issued via an OID4VCI workflow:

OID4VCI Tutorial Workflow

  1. The user launches the application and scans a QR code received from an issuer.
  2. The application displays what credential is being offered to the user and by what issuer.
  3. The user agrees to claiming the offered credential.
  4. The user is redirected to complete authentication.
  5. Upon successful authentication, the credential is issued to the userโ€™s application, where they can now store, view and present it.

The result will look something like this:

Prerequisites

Before we get started, letโ€™s make sure you have everything you need.

Prior knowledge

  • The issuance workflow described in this tutorial is based on the OID4VCI specification. If you are unfamiliar with this specification, refer to our Docs section for more information:

  • We assume you have experience developing iOS native apps in Swift.

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

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

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

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 device

  • Supported iOS device to run the built application on, setup with:
    • Biometric authentication (Face ID, Touch ID).
    • Available internet connection.

Got everything? Letโ€™s get going!

Environment setup

Perform the following steps to setup and configure your development environment:

Create a new project

Please follow the detailed instructions to Create a new Xcode project and add your organisationโ€™s identifier.

Create a new project

Unzip the dependencies file

  1. Unzip the MobileCredentialHolderSDK-*version*.xcframework.zip file.
  2. Drag the MobileCredentialHolderSDK-*version*.xcframework folder into your project.
  3. Configure MobileCredentialHolderSDK.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

Configure required resources

  1. Create a new file named Constants.swift within your project.

  2. Add the following string resources to represent the Authentication provider we will use for this tutorial:

    Swift
    enum Constants {
    	static let redirectUri: String = "io.mattrlabs.sample.mobilecredentialholderapp://credentials/callback"
    	static let clientId: String = "ios-sample-mobile-credential-holder-app"
    }
    • redirectUri : This is the path the SDK will redirect to once the user completes Authentication with the issuer. Our best practice recommendation is to configure this to be {redirect.scheme}://credentials/callback as shown in the example above. However, it can be any path as long as it is handled by your application and registered with the issuer against the corresponding clientId.
    • clientId : This is the identifier that is used by the issuer to recognize the wallet application. This is only used internally in the interaction between the wallet and the issuer and can be any string as long as it is registered with the issuer as a trusted wallet application.

Both of these parameters must be registered as a key pair as part of the issuerโ€™s OID4VCI workflow configuration. For this tutorial you will be claiming a credential from a MATTR Labs issuer which is configured with the parameters detailed above. We will help you configure your unique values as you move your implementation into production.

Add Bluetooth and biometric permissions

The SDK requires access to the mobile device Bluetooth and biometric capabilities for the different workflows built in this tutorial. Configure these permissions in the Info tab of the Application target:

Privacy capabilities

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

Nice work, your application is now all set to begin using the SDK!

Claiming a credential

In this part of the tutorial you will build the capabilities for an application user to interact with an OID4VCI Credential offer and claim an mDoc.

To achieve this, we will break this capability down into the following steps:

  1. Initialize the SDK.
  2. Interact with a Credential offer.
  3. Retrieve offer details and present them to the Holder.
  4. Obtain user consent and initiate Credential issuance.

Initialize the SDK

The first capability you will build into your app is to initialize the SDK so that your app can use SDK methods and classes. To achieve this, we need to import the MobilecredentialHolderSDK framework and then initialize the MobileCredentialHolder class:

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

    ContentView
    import SwiftUI
    // Claim Credential - Step 1.2: Import MobileCredentialHolderSDK
     
     
    struct ContentView: View {
        @ObservedObject var viewModel: ViewModel = ViewModel()
        var body: some View {
            NavigationStack(path: $viewModel.navigationPath) {
                VStack {
                    Button("Scan Credential Offer") {
                        viewModel.navigationPath.append(NavigationState.qrScan)
     
                    }
                    .padding()
                    createQRCodeButton
                    if viewModel.shouldDisplayOnlinePresentation {
                        Button("View Online Presentation Session") {
                            viewModel.navigationPath.append(NavigationState.onlinePresentation)
                        }
                        .padding()
     
                    }
                    Spacer()
                }
                .padding()
                .navigationDestination(for: NavigationState.self) { destination in
                    switch destination {
                    case .qrScan:
                        codeScannerView
                    case .credentialOffer:
                        credentialOfferView
                    case .retrievedCredentials:
                        retrievedCredentialsView
                    case .onlinePresentation:
                        // Online Presentation - Step 3.3: Display online presentation view
                        EmptyView()
                    case .presentCredentials:
                        qrCodeView
                    case .proximityPresentation:
                        // Proximity Presentation - Step 2.5: Display proximity presentation view
                        EmptyView()
                    }
                }
                // Online Presentation - Step 2.4: Create session from request URI
            }
        }
     
        // MARK: - Credential Retrieval Views
     
        var codeScannerView: some View {
            // Claim Credential - Step 2.4 Create QRScannerView
            EmptyView()
        }
     
        var credentialOfferView: some View {
            // Claim Credential - Step 3.3: Display Credential offer
            EmptyView()
        }
     
        var retrievedCredentialsView: some View {
            // Claim Credential - Step 4.4: Display retrieved credentials
            EmptyView()
        }
     
        // MARK: - Proximity Presentation Views
     
        var createQRCodeButton: some View {
            // Proximity Presentation - Step 1.5: Add button to generate QR code
            EmptyView()
        }
     
        func generateQRCode(data: Data) -> Data? {
            // Proximity Presentation - Step 1.6: Generate QR code
            return nil
        }
     
        var qrCodeView: some View {
            // Proximity Presentation - Step 1.7: Create QR code view
            EmptyView()
        }
    }
     
    class ViewModel: ObservableObject {
        @Published var navigationPath = NavigationPath()
        // Claim Credential - Step 1.3: Add MobileCredentialHolder var
     
        // Claim Credential - Step 3.1: Add DiscoveredCredentialOffer var
     
        // Claim Credential - Step 4.1: Add retrievedCredentials var
     
     
        // Proximity Presentation - Step 1.2: Create deviceEngagementString and proximityPresentationSession variables
     
     
        // Proximity and Online Presentation: Create variables for credential presentations
     
     
        // Online Presentation - Step 2.1: Create a variable to hold the online presentation session object
     
     
        var shouldDisplayOnlinePresentation: Bool {
            // Online Presentation - Step 3.4: View Online Presentation
            return false
        }
     
        // Claim Credential - Step 1.4: Initialize MobileCredentialHolder SDK
     
     
        @MainActor
        func getCredential(id: String) {
            // Proximity and Online Presentation: Retrieve a credential from storage
            print("This method will get a credential from storage and update the viewModel")
        }
    }
     
    // MARK: - Credential Retrieval
     
    extension ViewModel {
        @MainActor
        func discoverCredentialOffer(_ offer: String) {
            // Claim Credential - Step 3.2: Add discover credential offer logic
            print("This method will discover a credentials offer and update viewModel")
     
        }
     
        @MainActor
        func retrieveCredential() {
            // // Claim Credential - Step 4.2: Call retrieveCredential method
            print("This method will save a credential from offer and store it in the application's storage ")
        }
    }
     
    // MARK: - Online Presentation
     
    extension ViewModel {
        @MainActor
        func createOnlinePresentationSession(authorizationRequestURI: String) async {
            // Online Presentation - Step 2.3: Create online presentation session
            print("This method will create an online presentation session and update viewModel")
        }
     
        @MainActor
        func sendOnlinePresentationSessionResponse(id: String) {
            // Online Presentation - Step 4.1: Send online presentation response
            print("This method will be passed to a view and send a response with selected credentials")
        }
    }
     
    // MARK: - Proximity Presentation
     
    extension ViewModel {
        func createDeviceEngagementString() {
            // Proximity Presentation - Step 1.3: Create function to create a proximity presentation session and generate QR code
            print("This method will create a device engagement string that will be converted to a QR code")
        }
     
        // Proximity Presentation - Step 1.4: Update function signature
        func onRequestReceived() {
            // Proximity Presentation - Step 2.1: Store credential requests and matched credentials
            print("The signature of this method will need to be updated to include the correct parameters")
            print("This is a method that will be called when a proximity presentation request is received")
        }
     
        @MainActor
        func sendProximityPresentationResponse(id: String) {
            // Proximity Presentation - Step 3.1: Send a credential response
            print("This method will be passed to a view and send a response with selected credentials")
     
        }
    }
     
    // MARK: - Navigation
     
    enum NavigationState: Hashable {
        case qrScan
        case credentialOffer
        case retrievedCredentials
        case onlinePresentation
        case presentCredentials
        case proximityPresentation
    }

This will serve as the basic structure for your application. We will copy and paste different code snippets into specific locations to achieve the different functionalities. These locations are indicated by comments that reference both the section and the step (e.g. // Claim Credential - Step 1.2: Import MobileCredentialHolderSDK).

We recommend copying and pasting the comment text to easily locate it in the code.

  1. Add the following code after the // Claim Credential - Step 1.2: Import MobileCredentialHolderSDK comment to import MobileCredentialHolderSDK and enable using its capabilities in your application:

    ContentView
     import MobileCredentialHolderSDK
  2. Add the following code after the // Claim Credential - Step 1.3: Add MobileCredentialHolder var comment to create a variable that holds the mobileCredentialHolder instance:

    ContentView
        var mobileCredentialHolder: MobileCredentialHolder
  3. Add the following code after the // Claim Credential - Step 1.4: Initialize MobileCredentialHolder SDK comment to initialize the SDK and make it available for your application:

    ContentView
        init() {
            do {
                mobileCredentialHolder = MobileCredentialHolder.shared
                try mobileCredentialHolder.initialise(userAuthRequiredOnInitialise: false)
            } catch {
                print(error)
            }
        }
  4. Run the app to make sure it compiles properly.

Interact with a Credential offer

Interact with Credential offer

Once the SDK is initialized, the next step is to build the capability to handle a Credential offer, which can be received as deep-links or QR codes. As this tutorial uses an offer formatted as a QR code, your application needs to be able to scan and process it.

For ease of implementation, we will use a third party framework to achieve this:

  1. Add camera usage permissions to the app target:
Camera permissions
  1. Add the CodeScanner library via Swift Package Manager.
Code scanner package
  1. Create a new swift file named QRScannerView and add the following code into it to implement the QR scanning capability:

    QRScannerView
        import SwiftUI
        import CodeScanner
     
        struct QRScannerView: View {
     
            private let completionHandler: (String) -> Void
     
            init(completion: @escaping (String) -> Void) {
                completionHandler = completion
            }
     
            var body: some View {
                CodeScannerView(codeTypes: [.qr]) { result in
                    switch result {
                    case .failure(let error):
                        print(error.localizedDescription)
                    case .success(let result):
                        print(result.string)
                        completionHandler(result.string)
                    }
                }
            }
    }
  2. Return to the ContentView file and replace the EmptyView() under the // Claim Credential - Step 2.4 Create QRScannerView comment with the following code to create a new QRScannerView view in the application for scanning QR codes:

    ContentView
            QRScannerView(
                completion: { credentialOffer in
                    viewModel.discoverCredentialOffer(credentialOffer)
                }
            )
  3. Run the app and select the Scan Credential Offer button.

You should see a result similar to the following:

As the user selects the Scan Credential Offer button, the app launches the device camera to enable the user to scan a QR code.

You might notice that nothing happens after scanning a QR code - this is expected. In the next step we will implement the logic that retrieves the credential offer details from the QR code and presents them to the user.

Retrieve offer details and present them to the user

Present offer details

The next capability to build is to display the Credential offer details to the user before they agree to claim any credentials. Credential discovery is the process in which a wallet application retrieves the offer details, including:

  • What Issuer is offering the credentials?
  • What credentials are being offered, in what format and what claims do they include?

To display this information to the user, your application should call the SDKโ€™s discoverCredentialOffer method. We are going to implement this within the ViewModel class.

  1. Add the following code under the // Claim Credential - Step 3.1: Add DiscoveredCredentialOffer var comment to add a new variable that will hold the credential offer details:

    ContentView
    @Published var discoveredCredentialOffer: DiscoveredCredentialOffer?
  2. Replace the print statement under the // Claim Credential - Step 3.2: Add discover credential offer logic comment with the following code to create a function that calls the SDKโ€™s discoverCredentialOffer method:

    ContentView
        Task {
            do {
                discoveredCredentialOffer = try await mobileCredentialHolder.discoverCredentialOffer(offer)
                // present credential offer screen, as soon as credential offer is discovered
                navigationPath.append(NavigationState.credentialOffer)
            } catch {
                print(error)
            }
        }

    This function is called from our QRScannerView callback, so that when the user scans a QR Code that includes a credential offer, the discoverCredentialOffer method is called and accepts the returned credentialOffer string as its offer parameter.

    This is a URL-encoded Credential offer which in our example is embedded in a QR code. In other implementations you might have to retrieve this parameter from a deep-link.

    The discoverCredentialOffer method makes a request to the offer URL to retrieve the offer details and returns it as a DiscoveredCredentialOffer object:

    Swift
    struct DiscoveredCredentialOffer {
        let issuer: URL
        let authorizeEndpoint: URL
        let tokenEndpoint: URL
        let credentialEndpoint: URL
        let credentials: [OfferedCredential]
        let mdocIacasUri: URL?
    }

    The application can now use the issuer and credentials properties and present this information for the user to review. Once an application has discovered a credential offer, the user is navigated to the credentialOfferView view, which we are going to implement next.

  3. Replace EmptyView under the // Claim Credential - Step 3.3: Display Credential offer comment with the following code to navigate the user to the credentialOfferView view when a credential offer is discovered:

    ContentView
        VStack {
            Text("Received \(viewModel.discoveredCredentialOffer?.credentials.count ?? 0) Credential Offer(s)")
                .font(.headline)
            Text("from \(viewModel.discoveredCredentialOffer?.issuer.absoluteString ?? "unknown issuer")")
                .font(.subheadline)
            List(viewModel.discoveredCredentialOffer?.credentials ?? [], id: \.docType) { credential in
                Section {
                    HStack {
                        Text("Name:")
                            .bold()
                        Spacer()
                        Text("\(credential.name ?? "")")
                    }
                    HStack {
                        Text("Doctype:")
                            .bold()
                        Spacer()
                        Text("\(credential.docType)")
                    }
                    HStack {
                        Text("No. of claims:")
                            .bold()
                        Spacer()
                        Text("\(credential.claims.count)")
                    }
                }
            }
            Button {
                viewModel.retrieveCredential()
            } label: {
                Text("Consent and retrieve Credential(s)")
                    .font(.title3)
            }
            .buttonStyle(.borderedProminent)
            .clipShape(Capsule())
        }
  4. Run the app, select the Scan Credential Offer button and scan the following QR code:

QR Code

You should see a result similar to the following:

As the user scans the QR code, the application displays the credential offer details.

You might notice that nothing happens if you select the Consent and retrieve Credential(s) button. This is expected - in the next step we will implement the logic that initiates the credential issuance once the user provides their consent.

Present offer details

The next (and final!) step is to build the capability for the user to accept the credential offer. This should then trigger issuing the credential and storing it in the application storage.

Once the user provides their consent by selecting the Consent and retrieve Credential(s) button, your application must call the SDKโ€™s retrieveCredentials function to trigger the credential issuance and store the issued credential in the application storage.

  1. Add the following code under the // Claim Credential - Step 4.1: Add retrievedCredentials var comment to add a new variable that will hold the result returned by the SDKโ€™s retrieveCredentials method:

    ContentView
    @Published var retrievedCredentials: [MobileCredential] = []
  2. Replace the print statement under the // Claim Credential - Step 4.2: Call retrieveCredential method comment with the following code to create a new function that will call the SDKโ€™s retrieveCredentials method:

    ContentView
        Task {
            do {
                let retrievedCredentialResults = try await mobileCredentialHolder.retrieveCredentials(
                    credentialOffer: discoveredCredentialOffer!,
                    clientId: Constants.clientId,
                    redirectUri: Constants.redirectUri,
                    autoTrustMobileCredentialIaca: true)
                Task {
                    var credentials: [MobileCredential] = []
                    for result in retrievedCredentialResults {
                        if let credentialId = result.credentialId {
                            if let credential = try? await mobileCredentialHolder.getCredential(credentialId: credentialId) {
                                credentials.append(credential)
                            }
                        }
                    }
                    self.retrievedCredentials = credentials
                    // Clear navigation stack and display retrievedCredentials view
                    navigationPath = NavigationPath()
                    navigationPath.append(NavigationState.retrievedCredentials)
                }
            } catch {
                print(error.localizedDescription)
            }
        }

Letโ€™s review where we got all the parametersโ€™ mobileCredentialHolder.retrieveCredentials from:

Authentication

Once the function is called, it will redirect the user to authenticate with the configured Authentication provider defined in the authorizeEndpoint element of the DiscoveredCredentialOffer object.

Upon successful authentication, the user can proceed to complete the OID4VCI workflow configured by the issuer. This workflow can include different steps based on the issuerโ€™s configuration, but eventually the user is redirected to the configured redirectUri which should be handled by your wallet application.

As the user is redirected to the configured redirectUri, the issuer sends the issued mDocs to your wallet application. The SDK then processes the received credentials and validates them against the ISO/IEC 18013-5:2021 standard. Credentials who meet validation rules are stored in the application internal data storage.

The retrieveCredentials function then returns a RetrieveCredentialResult array, which includes metadata of all retrieved credentials:

RetrieveCredentialResult
struct RetrieveCredentialResult {
    let docType: String
    let credentialId: String?
    let error: RetrieveCredentialError?
}
 
[
   {
      "docType":"org.iso.18013.5.1.mDL",
      "credentialId":"F52084CF-8270-4577-8EDD-23149639D985"
   }
]
  • docType : Identifies the credential type.
  • credentialId : Internal unique identifier of this credential.

After the result is received, your application can retrieve specific credentials by calling the SDKโ€™s getCredential method with the credentialId of any retrieved credential.

The SDKโ€™s getCredential method returns a MobileCredential object which can be used to display the retrieved credential, its claims and verification status to the user.

Since this object can be used across multiple views, it will make sense to create one view that will represent it. We will use this view in both the Proximity and Online presentation tutorials.

  1. Create a new file named DocumentView and add the following content:

    DocumentView
    MobileCredentialHolderSDK 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)
     
                    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)
                    }
                }
                .padding()
                .background(RoundedRectangle(cornerRadius: 10).fill(Color.white).shadow(radius: 5))
                .padding(.horizontal)
            }
        }
     
        // MARK: DocumentViewModel
     
        class DocumentViewModel: ObservableObject {
            var docType: String
     
            var namespacesAndClaims: [String: [String: String?]]
     
            init(from credential: MobileCredential) {
                self.docType = credential.docType
                self.namespacesAndClaims = credential.claims?.reduce(into: [String: [String: String]]()) { result, outerElement in
                    let (outerKey, innerDict) = outerElement
                    result[outerKey] = innerDict.mapValues { $0.textRepresentation }
                } ?? [:]
            }
     
            init(from credentialMetadata: MobileCredentialMetadata) {
                self.docType = credentialMetadata.docType
                var result: [String: [String: String?]] = [:]
                credentialMetadata.claims?.forEach { namespace, claimIDs in
                    var transformedClaims: [String: String?] = [:]
                    claimIDs.forEach { claimID in
                        transformedClaims[claimID] = Optional<String>.none
                    }
                    result[namespace] = transformedClaims
                }
                self.namespacesAndClaims = result
            }
     
            init(from request: MobileCredentialRequest) {
                self.docType = request.docType
                self.namespacesAndClaims = request.namespaces.reduce(into: [String: [String: String?]]()) { result, outerElement in
                    let (outerKey, innerDict) = outerElement
                    result[outerKey] = innerDict.mapValues { _ in nil }
                }
            }
        }
     
        // 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 file comprises the following components:

    • DocumentViewModel: This class stores the credentialsโ€™ docType and claim values.
    • DocumentView: This view takes DocumentViewModel as a parameter and displays its content in a human-readable format.
    • MobileCredentialElementValue : This helper extension allows retrieving a MobileCredentialElementValue from a MobileCredentialโ€™s claims and present it in a human-readable format.
  2. Return to ContentView and replace EmptyView under the // Claim Credential - Step 4.4: Display retrieved credentials comment with the following code to use the DocumentView structure to display retrieved credentials to the user:

    ContentView
        ScrollView {
            VStack {
                Text("Retrieved Credentials")
                    .font(.title)
                ForEach(viewModel.retrievedCredentials, id: \.id) { credential in
                    DocumentView(viewModel: DocumentViewModel(from: credential))
                }
            }
        }
  3. Run the app, select the Scan Credential Offer button, scan the QR code and then select Consent and retrieve Credential(s).

QR Code

You should see a result similar to the following:

As the user scans the QR code, the application displays the offer details. The user then provides consent to retrieve the credentials and the wallet responds by initiating the issuance workflow and displaying the retrieved credentials to the user.

This tutorial uses a demo MATTR Labs Credential offer to issue the credential. This offer uses a workflow that doesnโ€™t actually authenticate the user before issuing a credential, but redirects them to select the credential they wish to issue. In production implementations this must be replaced by a proper Authentication provider to comply with the ISO/IEC 18013-5:2021 standard and the OID4VCI specification.

Credential claimed

Congratulations! Your application can now interact with an OID4VCI Credential offer to claim mDocs!

Summary

You have just used the iOS mDoc holder SDK to build a wallet application that can claim an mDoc issued via an OID4VCI workflow:

OID4VCI Tutorial Workflow

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

  1. Initialize the SDK so the application can use its functions and classes.
  2. Interact with a Credential offer formatted as a QR code.
  3. Retrieve the offer details and present them to the user.
  4. Obtain user consent and initiate the credential issuance workflow.

Whatโ€™s next?

  • You can build additional capabilities into your new application:
  • You can check out the iOS mDoc holder SDK Docs to learn more about available functions and classes.