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:
NfcReadDelegatefunc onNfcReadResults(results: NfcReadResults)func onNfcReadStarted()func onNfcReadFinished(error: NfcReadError?)
NfcReadEventsDelegatefunc onNfcReadEvent(event: NfcReadEvent)
The callbacks are received in this order:
onNfcReadStarted()when the SDK is on screen and the flow begins.onNfcReadResults(results:)only when the chip was read and verified successfully.onNfcReadFinished(error:)when the SDK has been dismissed, always as the last callback.errorisnilafter 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. |