Verify an mDL in-person/offline

Verify an ISO/IEC 18013-5 mDL over BlueTooth Low Energy

This guide is for iOS applications. Building for Android? Look here Verify an mDL in-person/offline

The structure for verifying an mDL is very similar to implementing the presentation of an mDL. To verify an mDL over BLE you will need to manage the BLEReaderSessionState and provide a UI for every state during the presentation process.

1. Scanning Documents

Rather than generating a QR-code, for a verifier, you will have to implement a scanning ability. For mdoc verification, you can directly use the QRCodeScanner. If you want to implement a generic scanner that can handle PDF417 VC Barcodes and Machine Readable Zones, check out this example of a Scanning Component.

Verify mdocs

Here, we give an example of what your VerifyMdocView could look like. You can also directly reference our example implementation.

import SpruceIDMobileSdk

// An Example View for Verification of Mdocs 
public struct VerifyMDocView: View {
    @Binding var path: NavigationPath
    @State private var scanned: String?
    
    public var body: some View {
        if scanned == nil {
            QRCodeScanner(
                        title: "Scan QR Code",
                        subtitle: "Looking...",
                        onRead: { code in
                        self.scanned = code
                        },
                        onCancel: onCancel,
                        titleFont: .customFont(font: .inter, style: .bold, size: .h0),
                        subtitleFont: .customFont(font: .inter, style: .bold, size: .h4),
                        cancelButtonFont: .customFont(font: .inter, style: .medium, size: .h3),
                        readerColor: .white
            )
        } else {
            // Define an MdocReaderView and initiate it when the code is scanned
            MDocReaderView(
                // Set the scanned value
                uri: scanned!,
                // Define the items you want to query by namespace, attribute_name 
                // and intent to retain:
                requestedItems: ["org.iso.18013.5.1": ["given_name": true]],
                // Enter an array of issuer certificates that you accept (e.g. a list of US states).
                // These issuer certificates should follow the IACA profile as defined in ISO/IEC 18013-5 Annex B. 
                trustAnchorRegistry: [<issuer_cert>],
                onCancel: onCancel,
                path: $path
            )
        }
    }
    
    func onCancel() {
        // Define what to do when a user cancels the scan
        self.scanned = nil
        path.removeLast()
    }
}

MdocReaderView

Build the Delegate that holds the BLEReaderSessionState and the mDocReader sessionManager.

// Build out your Reader Delegate

class MDocReaderDelegate: ObservableObject {
    @Published var state: BLEReaderSessionState = .advertizing
    private var mdocReader: MDocReader?
    
    init(
        uri: String,
        requestedItems: [String: [String: Bool]], 
        trustAnchorRegistry: [String]?
    ) {
        // MdocReader is a core SpruceKit Component.
        self.mdocReader = MDocReader(
            callback: self,
            uri: uri,
            requestedItems: requestedItems,
            trustAnchorRegistry: trustAnchorRegistry
        )
    }
    
    func cancel() {
        self.mdocReader?.cancel()
    }
}

// Make sure to extend the BLEReaderSessionStateDelegate to MdocReaderDelegate
extension MDocReaderDelegate: BLEReaderSessionStateDelegate {
    public func update(state: BLEReaderSessionState) {
        self.state = state
    }
}

Once you have your Delegate implemented, you can add it to your MdocReaderView, which manages the UI based on the SessionState.

// Build your MdocReaderView
public struct MDocReaderView: View {
    @StateObject var delegate: MDocScanViewDelegate
    @Binding var path: NavigationPath
    var onCancel: () -> Void
    
    init(
        uri: String,
        requestedItems: [String: [String: Bool]],
        trustAnchorRegistry: [String]?,
        onCancel: @escaping () -> Void,
        path: Binding<NavigationPath>
    ) {
        self._delegate = StateObject(
            wrappedValue: MDocReaderDelegate(
                uri: uri,
                requestedItems: requestedItems,
                trustAnchorRegistry: trustAnchorRegistry
            )
        )
        self.onCancel = onCancel
        self._path = path
    }
    
    @ViewBuilder
    var cancelButton: some View {
        Button("Cancel") {
            self.cancel()
        }
        .padding(10)
        .buttonStyle(.bordered)
        .tint(.red)
 
    
    public var body: some View {
        VStack {
            switch self.delegate.state {
                // Manage the BLE ReaderSessionStates here
            }
        }
        .padding(.all, 30)
        .navigationBarBackButtonHidden(true)
    }
    
    func cancel() {
        self.delegate.cancel()
        self.onCancel()
    }
}

There you have it, you now have everything you need for an mDL verification flow. You can:

  • Initiate a QR Code Scanner

  • Manage the states of the BleReaderSession with appropriate UI of your design

  • Configure your own TrustAnchors

  • Call the MdocReader component to verify the mdoc

Last updated

Was this helpful?