NFC chip reading
Read the NFC chip of a passport or ID card with MrtdStore on iOS and Android.
Preview
This page describes a pre-release version of the SDK. Names, versions and APIs on this page can change before the release.
Passports and many identity cards carry a chip with the holder's data: an eMRTD (electronic
machine readable travel document). MrtdStore reads that chip over NFC. The SDK does the whole
chip protocol itself: it opens the NFC session, establishes the secure connection with the chip
from the document's key data, reads the data groups you select and closes the session.
You meet the chip reading in two places:
- In an inquiry. At a government ID NFC step, the ready-made inquiry UI
mounts an
MrtdStore, reads the chip and submits the step. If you use the ready-made UI, you do not need to useMrtdStoreyourself. - In your own UI for the inquiry. If you render the NFC step yourself, you drive
MrtdStoreas described below.
Platform availability
| Platform | Chip reading |
|---|---|
| iOS | Yes, with Core NFC. |
| Android | Yes, on devices with an NFC reader that is switched on. |
| Web | No. Browsers have no NFC reader. MrtdStore exists, but every read fails with ChipNotSupported. |
On the web, as on a phone without an NFC reader, the message of that error is "This device can’t
read document chips. Use a phone with NFC." in the runtime's current locale. The web inquiry UI
shows the screens of an NFC step and runs their buttons, but reads no chip.
Platform setup
- Android. The SDK's manifest declares the permission
android.permission.NFCand the featureandroid.hardware.nfcwithandroid:required="false", so your app installs on devices without NFC. The read needs an activity in the foreground. See Android setup. - iOS. The read uses a Core NFC tag reader session. See iOS setup for the settings your app needs.
What the chip holds
The chip stores its data in data groups, protected by the Document Security Object (SOD).
DataGroupSelection picks what to read: it has one boolean per file, sod and dg1 to dg16.
Two static functions of FolioSdk return ready-made selections:
FolioSdk.mrtdDataGroupsStandard()returns the standard selection, the one the ready-made UI reads.FolioSdk.mrtdDataGroupsAll()returns a selection of every file, SOD and DG1 to DG16.
The standard selection reads:
| Data group | Read by the standard selection |
|---|---|
| SOD | yes |
| DG1 | yes |
| DG2 | yes |
| DG7 | yes |
| DG11 | yes |
| DG12 | yes |
| DG3 to DG6, DG8 to DG10, DG13 to DG16 | no |
You can also build a DataGroupSelection yourself, with a value for each of its 17 fields.
let standard = try FolioSdk.mrtdDataGroupsStandard()
let all = try FolioSdk.mrtdDataGroupsAll()Required and optional data groups
The reader treats the files of a selection in two ways:
- SOD, DG1 and DG2 are required. When the selection includes one of them and it cannot be read, the read fails.
- DG3 to DG16 are optional. The reader skips one that the chip does not list, and one that the chip reports as absent (file not found, or referenced data not found). Any other failure while it reads an optional data group fails the whole read.
The reader also reads DG14 when the chip needs it for chip authentication: for a residence permit, and for a chip that lists DG3 or DG4. If that DG14 cannot be parsed, the read fails. When that DG14 says the chip supports chip authentication, the reader performs it. A failed chip authentication fails the read of a residence permit; for other documents the read goes on without it.
Mount and read
MrtdStore mounts on any runtime that is an MrtdHost. FolioSdk is one. Send Start with the
document's key data, then watch phase:
let mrtd = try MrtdStore.mount(runtime: sdk)
let reading = mrtd.$state
.receive(on: DispatchQueue.main)
.sink { model in
switch model.phase {
case .complete: print("read:", model.dataGroups, model.totalBytes)
case .failed: print("failed:", String(describing: model.error))
case .idle, .reading, .cancelled: break
}
}
let selection = try FolioSdk.mrtdDataGroupsStandard()
try mrtd.dispatch(action: .reset)
try mrtd.dispatch(action: .start(
documentType: .passport,
documentNumber: documentNumber,
dateOfBirth: dateOfBirth,
dateOfExpiry: dateOfExpiry,
can: nil,
selection: selection
))MrtdStore also has the parameterless subscribe(), which returns an MrtdSubscription. Its
async next() returns the current MrtdUiModel first, then the latest state after each change
(intermediate states can be skipped), and returns no model once the store is closed, the same way
as the InquirySubscription described in Inquiry store.
On the web this read always ends in Failed with ChipNotSupported.
Close the store when the screen that reads the chip goes away. mount and dispatch throw on
failure: FolioSDKError on iOS and the web, FolioSDKException on Android. See
Errors.
In Compose, rememberMrtdStore() mounts the store on the runtime of the enclosing
FolioSDKProvider or MrtdHostProvider and closes it when it leaves the composition. In React,
useMrtdStore() from @folio/sdk/stores mounts the store on the runtime of the enclosing
FolioSDKProvider or MrtdHostProvider, closes it on unmount and returns { store, state }.
Both are undefined until the store is mounted.
Actions
| Action | Fields | What it does |
|---|---|---|
Start | documentType, documentNumber, dateOfBirth, dateOfExpiry, can, selection | Clears the previous result and reads the chip. phase becomes Reading. |
Cancel | Cancels the NFC session. phase becomes Cancelled. | |
Reset | Returns the store to Idle with no result. If a read is running, it cancels the NFC session. |
A result that arrives after Cancel or Reset is dropped.
The fields of Start:
| Field | Meaning |
|---|---|
documentType | MrtdDocumentType: Passport, IdCard, DriversLicense or ResidencePermit. |
documentNumber | The document number. The SDK removes spaces, upper-cases it and fits it to the 9-character field of the machine readable zone. |
dateOfBirth | The date of birth as 6 digits, YYMMDD, or as an ISO date, YYYY-MM-DD. |
dateOfExpiry | The date of expiry as 6 digits, YYMMDD, or as an ISO date, YYYY-MM-DD. |
can | Optional. The card access number, for documents that use it. |
selection | The DataGroupSelection to read. |
Document number, date of birth and date of expiry are the key the chip requires before it lets the
reader in. A date in neither form, or an empty document number, fails the read with
InvalidMrz before the NFC session opens.
| Action | Swift | Kotlin | TypeScript |
|---|---|---|---|
| Start | .start(documentType:documentNumber:dateOfBirth:dateOfExpiry:can:selection:) | MrtdAction.Start(documentType, documentNumber, dateOfBirth, dateOfExpiry, can, selection) | MrtdAction.start(documentType, documentNumber, dateOfBirth, dateOfExpiry, can, selection) |
| Cancel | .cancel | MrtdAction.Cancel | MrtdAction.cancel |
| Reset | .reset | MrtdAction.Reset | MrtdAction.reset |
can is optional: String? in Swift and Kotlin, string | undefined in TypeScript.
| Document type | Swift | Kotlin | TypeScript |
|---|---|---|---|
| Passport | .passport | MrtdDocumentType.Passport | MrtdDocumentType.passport |
| ID card | .idCard | MrtdDocumentType.IdCard | MrtdDocumentType.idCard |
| Driver's license | .driversLicense | MrtdDocumentType.DriversLicense | MrtdDocumentType.driversLicense |
| Residence permit | .residencePermit | MrtdDocumentType.ResidencePermit | MrtdDocumentType.residencePermit |
In TypeScript a document type is a value with a type: PASSPORT, ID_CARD, DRIVERS_LICENSE or
RESIDENCE_PERMIT.
State: MrtdUiModel
| Field | Type | Meaning |
|---|---|---|
phase | MrtdPhase | Where the read is. |
dataGroups | list of strings | The names of the files read, once phase is Complete. |
totalBytes | 64-bit integer | The total size of the files read, in bytes. |
error | MrtdUiError, optional | Why the read failed, once phase is Failed. |
totalBytes is UInt64 in Swift, Long in Kotlin and bigint in TypeScript.
| Phase | Meaning | Swift | Kotlin | TypeScript type |
|---|---|---|---|---|
Idle | No read has started, or the store was reset. | .idle | MrtdPhase.Idle | IDLE |
Reading | The NFC session is open or the chip is being read. | .reading | MrtdPhase.Reading | READING |
Complete | The chip was read. | .complete | MrtdPhase.Complete | COMPLETE |
Cancelled | The read was cancelled with Cancel. | .cancelled | MrtdPhase.Cancelled | CANCELLED |
Failed | The read failed. error says why. | .failed | MrtdPhase.Failed | FAILED |
The store does not hand you the chip's data. It keeps the files of the latest read inside the runtime, and the inquiry's NFC step encrypts and uploads them to Folio when you submit the step. Each new read replaces the files of the previous one.
Errors
MrtdUiError says why a read failed. Every case has a message: the text to show the person, in
the runtime's current locale. The SDK chooses it for the cause of the failure; it does not carry
the technical details.
| Error | Swift | Kotlin | TypeScript type | Cause |
|---|---|---|---|---|
InvalidMrz | .invalidMrz(message:) | MrtdUiError.InvalidMrz | INVALID_MRZ | The key data is not valid: a date is neither YYMMDD nor YYYY-MM-DD, or the document number is empty. |
ConnectionLost | .connectionLost(message:) | MrtdUiError.ConnectionLost | CONNECTION_LOST | The connection to the chip broke, or the NFC session was cancelled, not found or not available. |
Authentication | .authentication(message:) | MrtdUiError.Authentication | AUTHENTICATION | The chip refused access or an authentication step with the chip failed. Most often the key data does not match the document. |
ChipNotSupported | .chipNotSupported(message:) | MrtdUiError.ChipNotSupported | CHIP_NOT_SUPPORTED | The device has no NFC reader, the SDK has no NFC access on the platform (always the case in a browser), or the chip does not support the read. |
ReadFailed | .readFailed(message:) | MrtdUiError.ReadFailed | READ_FAILED | NFC is switched off on the device, a file could not be read or parsed, or the platform refused the NFC session. |
Internal | .internal(message:) | MrtdUiError.Internal | INTERNAL | Any other failure, for example of the secure connection, a certificate or the cryptography. |
The message for each cause, in English:
| Cause | Error | message |
|---|---|---|
| The device has no NFC reader, or the SDK has no NFC access on the platform | ChipNotSupported | This device can’t read document chips. Use a phone with NFC. |
| The device has an NFC reader, but it is switched off | ReadFailed | NFC is turned off. Turn it on in your device settings and try again. |
| The chip does not support what the read needs | ChipNotSupported | This document chip is not supported. |
| The key data is not valid, or an authentication step failed | InvalidMrz, Authentication | Chip authentication failed. Check the document details and try again. |
| The connection to the chip broke | ConnectionLost | Connection lost. Hold your phone near the document again. |
| Any other failure | ReadFailed, Internal | Couldn’t read the document chip. |
The SDK checks whether the device can read NFC before it opens the NFC session; a device that cannot never opens one. NFC that is switched off is reported when the session is opened.
On iOS each case carries its message as an associated value, for example
case let .connectionLost(message). On Android it is the message property of each case, and in
TypeScript it is error.value.message.
A failure while reading an optional data group fails the whole read with the error of that
failure: ReadFailed when the chip answers with an error, ConnectionLost when the connection
breaks. See Required and optional data groups.
What the platforms report
- iOS. While the system NFC sheet is open, it shows "Hold your phone near the document." in the
runtime's current locale. A tag that is not an ISO 7816 identity document is not read: the sheet
says "This NFC tag is not an ISO 7816 identity document." and keeps looking. When NFC reading is
not available on the device, the read fails with
ChipNotSupportedand no sheet opens. The read also fails when another NFC session is still running. - Android. When the device has no NFC reader, the read fails with
ChipNotSupported; when NFC is switched off, withReadFailedand the message that asks to turn it on. The read also fails when no activity is in the foreground, when no document is held to the reader within 60 seconds, and when another NFC session is still running. Only one NFC session can run at a time.
The NFC step of an inquiry
In an inquiry, the chip is read at a GovernmentIdNfc verification step. Its state is a
GovIdNfcStateView. See Inquiry store for how steps
work.
| State | Fields | What to show |
|---|---|---|
Info | title, description, buttons, optional illustration and processing | An introduction to the chip reading. |
Details | optional title and description, content (screen containers), optional processing | Details as a screen. |
Scan | title, description, documentType, optional hint, buttons, optional keyData, required, optional illustration and processing | The scan screen that reads the chip. |
Error | a VerificationFailureView | Why the step failed, and its buttons. |
keyData of Scan is an NfcKeyDataView with the key data the inquiry already knows about the
document, each optional: documentNumber, dateOfBirth, dateOfExpiry and mrz.
When Folio sends no key data, the SDK takes the document number, the date of birth and the date
of expiry of the inquiry's verified government ID, with the dates as YYYY-MM-DD, when all three
are known.
The ready-made UI runs the Scan state like this, and your own UI can do the same:
- When the person taps the button whose intent is
Submit, keep that intent and do not send it yet. - If
keyDatalacks the document number, the date of birth or the date of expiry, show thechrome.nfc.missingKeytext of the inquiry state and stop. - Send
Reset, thenStartwith the key data, the standard selection and the document type that matchesdocumentType, anNfcDocumentType:MrtdDocumentTypePassportforPassportandIdCardforIdCard. Match both cases; there is no other. - When
phasebecomesComplete, send the keptSubmitintent to theInquiryStore. The inquiry then uploads the files and Folio checks the chip data. - When
phasebecomesFailed, show the error'smessage, the text the SDK chose for the failure (see Errors). The ready-made UI falls back to thechrome.nfctext for the case when the message is empty:authenticationErrorforInvalidMrzandAuthentication,connectionLostforConnectionLost,chipNotSupportedforChipNotSupported, andreadFailedforReadFailedandInternal.
A failure of the check at Folio arrives in the InquiryStore as an InquiryError of type GovId
whose reason starts with Nfc, such as NfcPaFailed or NfcDg1Missing. See
Errors.