Skip to content

API

This section covers the native iOS API: how to start the flow, configure the SDK, implement the available delegates, and read the results, errors and events it returns.

Delegate protocols

Use these protocols to receive lifecycle callbacks and events:

  • NfcReadDelegate
    • func onNfcReadResults(results: NfcReadResults)
    • func onNfcReadStarted()
    • func onNfcReadFinished(error: NfcReadError?)
  • NfcReadEventsDelegate
    • func onNfcReadEvent(event: NfcReadEvent)

The callbacks are received in this order:

  1. onNfcReadStarted() when the SDK is on screen and the flow begins.
  2. onNfcReadResults(results:) only when the chip was read and verified successfully.
  3. onNfcReadFinished(error:) when the SDK has been dismissed, always as the last callback. error is nil after a successful read.

Invalid configuration

If the configuration is not valid, the SDK does not start: onNfcReadFinished(error:) is called right away with configValidationError, and onNfcReadStarted() is never called.

Starting a flow

1) Create an NfcReadConfiguration (build it in code or decode it from JSON).

2) Set the events delegate (optional but recommended).

3) Start the SDK with your delegate and configuration.

import UIKit
import VeridasNfcSDK
import VeridasCommonDefinitions

final class MyController: UIViewController, NfcReadDelegate, NfcReadEventsDelegate {
    func startNfcRead() {
        let config = loadConfigFromJSON("nfcRead", as: NfcReadConfiguration.self)
        NfcRead.setEventsDelegate(delegate: self) // NfcReadEventsDelegate
        NfcRead.start(delegate: self, configuration: config) // NfcReadDelegate
    }

    // NfcReadDelegate
    func onNfcReadResults(results: NfcReadResults) { print(results) }
    func onNfcReadStarted() { print("SDK started") }
    func onNfcReadFinished(error: NfcReadError?) { print(error as Any) }

    // NfcReadEventsDelegate
    func onNfcReadEvent(event: NfcReadEvent) { print(event) }
}

Example helper for loading JSON configs (shared by all SDKs):

private func loadConfigFromJSON<T: Decodable>(_ name: String, as type: T.Type) -> T {
    guard let url = Bundle.main.url(forResource: name, withExtension: "json") else {
        fatalError("Missing \(name).json in app bundle")
    }
    do {
        let data = try Data(contentsOf: url)
        let decoder = JSONDecoder()
        return try decoder.decode(T.self, from: data)
    } catch {
        fatalError("Failed decoding \(name).json: \(error)")
    }
}

Providing the document data at runtime

The data that opens the chip usually comes from a previous step of your flow (for example, the OCR of a document capture) rather than from a static JSON file. Set it on the configuration before starting the SDK. When it is complete and valid, the form is skipped and the reading starts right away:

var config = loadConfigFromJSON("nfcRead", as: NfcReadConfiguration.self)
var setup = config.setup ?? NfcSetup()
setup.setPrefill(NfcPrefill(documentNumber: "BAA000589",
                            dateOfBirth: "860816",    // MRZ format: yyMMdd
                            dateOfExpiry: "281127"))  // MRZ format: yyMMdd
setup.setKeys(["DG1_NAME", "DG1_SURNAME", "DG1_DOC_NUMBER", "DG1_DOB"])
config.setSetup(setup)

NfcRead.start(delegate: self, configuration: config)

The keys to return usually come at runtime too: when your flow uses the Veridas services, pass the nfcKeys they return for the document.

See prefill for the validation rules.

ViewModel example

import Foundation
import VeridasCommonDefinitions
import VeridasNfcSDK

final class MainSdkViewModel: ObservableObject {
    func startNfcRead() {
        let config = loadConfigFromJSON("nfcRead", as: NfcReadConfiguration.self)
        NfcRead.setEventsDelegate(delegate: self)
        NfcRead.start(delegate: self, configuration: config)
    }

    private func loadConfigFromJSON<T: Decodable>(_ name: String, as type: T.Type) -> T {
        guard let url = Bundle.main.url(forResource: name, withExtension: "json") else {
            fatalError("Missing \(name).json in app bundle")
        }
        do {
            let data = try Data(contentsOf: url)
            let decoder = JSONDecoder()
            return try decoder.decode(T.self, from: data)
        } catch {
            fatalError("Failed decoding \(name).json: \(error)")
        }
    }
}

extension MainSdkViewModel: NfcReadDelegate, NfcReadEventsDelegate {
    func onNfcReadResults(results: NfcReadResults) { print("onNfcReadResults \(results)") }
    func onNfcReadStarted() { print("onNfcReadStarted") }
    func onNfcReadEvent(event: NfcReadEvent) { print("onNfcReadEvent \(event)") }
    func onNfcReadFinished(error: NfcReadError?) { print("onNfcReadFinished \(String(describing: error))") }
}

Results

onNfcReadResults(results:) receives an NfcReadResults:

public struct NfcReadResults: Equatable, Sendable, CustomStringConvertible {
    public let keysData: [String: String]
    public let certificate: NfcCertificate
    public let authentication: NfcAuthentication
    public let dataGroupHashes: [NfcDataGroupHash]
}

The results are only delivered when the document passed every authenticity check that was performed. A document whose checks fail is not delivered: the SDK finishes with one of the authentication errors described in Errors.

Printing the results

The description of NfcReadResults (what print(results) shows) lists the keys received, the authentication outcomes, the certificate issuer and how many data group hashes are valid, but not the values: they are the holder's personal data. Read them from keysData.

NfcReadResults and the types it contains have public initialisers, so you can build them in your own tests.

keysData

The data read from the chip, keyed by field.

Only the requested keys are returned

keysData only contains the text keys listed in keys. If keys is not set or is empty, it contains no text key. The photos (cut_photo and signature) are the exception: they are always returned when the chip carries them, whether they are listed or not.

A requested key is absent when the chip does not carry that value (the data group was not read, the element is not in it, or it has no such part, such as an address with no region). A value the chip carries empty is "".

Text values are cleaned: the < filler characters of the MRZ are replaced by spaces, repeated spaces are collapsed and the result is trimmed. Dates use the dd MM yyyy format.

Key
Value
DG1_NAME Given names of the holder. Taken from DG11 when it was read, since DG1 truncates the name to the MRZ width; from DG1 otherwise.
DG1_SURNAME Surnames of the holder, with the same source as DG1_NAME. A name with a single part is the surname, and DG1_NAME is absent.
DG1_DOC_NUMBER Document number.
DG1_DOC_TYPE Document type, as in the MRZ (for example ID, or P for passports).
DG1_DOB Date of birth. Taken from DG11 when it carries a full date, from DG1 otherwise.
DG1_DOE Date of expiry.
DG1_ISSUER Issuing state or organization (for example ESP).
DG1_NATIONALITY Nationality of the holder.
DG1_SEX F or M. Absent for any other value.
DG1_OPT_DATA Optional data of the MRZ.
DG11_PERS_NUM Personal number from DG11. When DG11 does not carry it, the optional data of the MRZ.
DG1_ADDR_LINE, DG1_ADDR_CITY, DG1_ADDR_REGION Permanent address from DG11: street, city and region.
DG11_BIRTH_PLACE, DG11_BIRTH_STATE Place of birth from DG11: city and state or province.
DG11_TITLE Title of the holder from DG11.
cut_photo Face image of the holder (DG2), as a base64-encoded PNG.
signature Signature image of the holder (DG7), as a base64-encoded PNG.

Available data depends on the document

Not every document carries every data group. DG1 (MRZ data) and DG2 (face image) are always present on an eMRTD, while DG7 and DG11 are optional and depend on the issuer.

certificate

NfcCertificate, the Document Signer certificate that signed the chip's Document Security Object (SOD).

Property Description
issuer Issuer of the certificate.
notBefore Start of the validity period, e.g. "Feb 9 00:00:00 2021 GMT".
notAfter End of the validity period, in the same format.
encoded The certificate in ASN.1 DER. Empty when the SOD carries none.

authentication

NfcAuthentication, the result of every authentication protocol run against the chip. Each one is an NfcProtocolResult: success ("SUCCESS"), failed ("FAILED") or notPerformed ("NOT_PERFORMED").

Property
Description
pace PACE, the access protocol tried first.
bac BAC, the access protocol used when PACE is not available or fails.
chipAuthentication Chip Authentication (anti-cloning), performed when the chip has DG14.
activeAuthentication Active Authentication (anti-cloning), performed when the chip has DG15.
paceProtocol The PACE variant the chip was opened with, e.g. "id-PACE-ECDH-GM-AES-CBC-CMAC-256 / brainpoolP384r1". nil when PACE did not succeed.

A document usually implements only one of the two anti-cloning mechanisms. The Spanish DNIe, for example, uses Chip Authentication, so activeAuthentication == .notPerformed is expected. If both are notPerformed, no anti-cloning check could be run on that document.

dataGroupHashes

One NfcDataGroupHash per data group read, with the result of Passive Authentication for it. The array is empty when the chip has no SOD.

Property
Description
dgNumber The data group number (1 for DG1, 2 for DG2...).
readHash The hash stored in the SOD. nil when the SOD has none for this group.
calculatedHash The hash of the bytes read from the chip. nil when it could not be calculated.
isValid true when both hashes exist and are equal.

Errors

onNfcReadFinished(error:) receives an NfcReadError, or nil when the flow ended after a successful read. It has these properties: error, with the error type; info, an optional text with extra details (for example, the invalid configuration value, or the reason why Passive Authentication failed); and cause, reserved for future use and currently always nil.

Error
Description
nfcUnavailable The device cannot read NFC tags (for example, an iPad or a model without NFC). The SDK closes before showing any screen.
configValidationError The configuration is not valid. info describes the problem. See Validation.
passiveAuthenticationFailed The document could not be verified: its signer does not chain to a trusted CSCA, the SOD signature is not valid, or a data group does not match its hash in the SOD. info says why.
chipAuthenticationFailed The chip failed Chip Authentication: it may be a clone.
activeAuthenticationFailed The chip failed Active Authentication: it may be a clone.
maxRetriesReached A reading failed after the user had used all the retries set in maxRetries, whatever the failure.
userClose The user left the process.
forcedClose The host app closed the SDK with NfcRead.stop().
authenticationFailed, timeout, readingFailed, unsupportedDocument Recoverable reading failures. The SDK lets the user retry them, so they are only returned if the user leaves the process (or the host app calls NfcRead.stop()) while the failure is pending: the failure is returned instead of userClose or forcedClose. After a cancelled reading or a focus loss, leaving returns userClose.
genericError An unexpected error.

The authentication errors do not consume a reading attempt: they end the flow at once, because reading the same document again would give the same result.

Dispatched events

The SDK dispatches a set of events to communicate that the corresponding actions have been taken. Each event is sent to onNfcReadEvent(event:), which receives a Codable object:

    public struct NfcReadEvent: Codable, CustomStringConvertible

This object contains 2 main attributes, type with the event identifier and detail with all the information related to the event. The fields specific to a single event are optional, so make sure your code handles missing values.

Event Name
Event Detail Event Trigger
VD_mounted "sdkName": "VD-NFC", "type": "info", "flowType": "nfc" When the SDK is mounted.
VD_loaded "sdkName": "VD-NFC", "type": "info", "flowType": "nfc" When the SDK is fully loaded.
VD_nfcFlowStarted "sdkName": "VD-NFC", "type": "info", "flowType": "nfc" When the reading flow starts.
VD_instructions "sdkName": "VD-NFC", "type": "info", "flowType": "nfc" When the instructions are displayed.
VD_nfcMrzEntered "sdkName": "VD-NFC", "type": "info", "flowType": "nfc" When the user submits valid data in the document data form.
VD_nfcReadStarted "sdkName": "VD-NFC", "type": "info", "flowType": "nfc" When a chip reading attempt starts.
VD_nfcWrongKey "sdkName": "VD-NFC", "type": "error", "flowType": "nfc" When the chip could not be opened with the document data provided (followed by VD_nfcReadFailed, since it uses a retry).
VD_nfcReadFailed "sdkName": "VD-NFC", "type": "error", "flowType": "nfc", "retries": 1 When a reading attempt fails because the document was not detected, the reading was interrupted, the document is not supported, the chip could not be opened with the document data or the user cancelled the system NFC sheet. retries is the number of failed attempts so far: the SDK ends with maxRetriesReached when it goes over maxRetries.
VD_nfcCancelled "sdkName": "VD-NFC", "type": "info", "flowType": "nfc" When the user cancels the system NFC sheet (followed by VD_nfcReadFailed, since it uses an attempt), or leaves the process from the form or a feedback screen.
VD_loseFocus "sdkName": "VD-NFC", "type": "info", "flowType": "nfc" When the app goes to the background during a chip reading. The reading is interrupted and the focus loss feedback screen is shown, so the user can restart the process.
VD_nfcPassiveAuthChecked "sdkName": "VD-NFC", "type": "info", "flowType": "nfc", "accessProtocol": "PACE", "correctlySigned": true, "dataIntegrityVerified": true, "chipAuthentication": "SUCCESS", "activeAuthentication": "NOT_PERFORMED", "trustworthy": true, "paceProtocol": "id-PACE-ECDH-GM-AES-CBC-CMAC-256 / brainpoolP384r1", "initialSelectedAID": "A0000002471001", "readBlockSize": 223 When the chip has been read and its authenticity has been verified.
VD_nfcReadSucceeded "sdkName": "VD-NFC", "type": "info", "flowType": "nfc" When the reading finishes successfully.
VD_restartClicked "sdkName": "VD-NFC", "type": "info", "flowType": "nfc" When the retry or restart button is clicked in a feedback screen.
VD_closeClicked "sdkName": "VD-NFC", "type": "info", "flowType": "nfc" When the user confirms leaving the process in the exit confirmation modal.
VD_unmounted "sdkName": "VD-NFC", "type": "info", "flowType": "nfc" When the SDK is unmounted.

The fields of VD_nfcPassiveAuthChecked:

Field
Description
accessProtocol The protocol that opened the chip: PACE or BAC.
correctlySigned Whether the Document Signer chains to a trusted CSCA and the SOD signature is valid.
dataIntegrityVerified Whether every data group read matches its hash in the SOD.
chipAuthentication, activeAuthentication SUCCESS, FAILED or NOT_PERFORMED.
trustworthy Whether the document passed every check that was performed.
paceProtocol The PACE variant the chip was opened with. Absent when PACE did not succeed.
initialSelectedAID The eMRTD application identifier iOS detected the chip through, e.g. A0000002471001.
readBlockSize Bytes read per command at the end of the reading: 223, or 160 when the chip did not accept 223.