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 use MrtdStore yourself.
  • In your own UI for the inquiry. If you render the NFC step yourself, you drive MrtdStore as described below.

Platform availability

PlatformChip reading
iOSYes, with Core NFC.
AndroidYes, on devices with an NFC reader that is switched on.
WebNo. 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.NFC and the feature android.hardware.nfc with android: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 groupRead by the standard selection
SODyes
DG1yes
DG2yes
DG7yes
DG11yes
DG12yes
DG3 to DG6, DG8 to DG10, DG13 to DG16no

You can also build a DataGroupSelection yourself, with a value for each of its 17 fields.

Swift
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:

Swift
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

ActionFieldsWhat it does
StartdocumentType, documentNumber, dateOfBirth, dateOfExpiry, can, selectionClears the previous result and reads the chip. phase becomes Reading.
CancelCancels the NFC session. phase becomes Cancelled.
ResetReturns 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:

FieldMeaning
documentTypeMrtdDocumentType: Passport, IdCard, DriversLicense or ResidencePermit.
documentNumberThe document number. The SDK removes spaces, upper-cases it and fits it to the 9-character field of the machine readable zone.
dateOfBirthThe date of birth as 6 digits, YYMMDD, or as an ISO date, YYYY-MM-DD.
dateOfExpiryThe date of expiry as 6 digits, YYMMDD, or as an ISO date, YYYY-MM-DD.
canOptional. The card access number, for documents that use it.
selectionThe 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.

ActionSwiftKotlinTypeScript
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.cancelMrtdAction.CancelMrtdAction.cancel
Reset.resetMrtdAction.ResetMrtdAction.reset

can is optional: String? in Swift and Kotlin, string | undefined in TypeScript.

Document typeSwiftKotlinTypeScript
Passport.passportMrtdDocumentType.PassportMrtdDocumentType.passport
ID card.idCardMrtdDocumentType.IdCardMrtdDocumentType.idCard
Driver's license.driversLicenseMrtdDocumentType.DriversLicenseMrtdDocumentType.driversLicense
Residence permit.residencePermitMrtdDocumentType.ResidencePermitMrtdDocumentType.residencePermit

In TypeScript a document type is a value with a type: PASSPORT, ID_CARD, DRIVERS_LICENSE or RESIDENCE_PERMIT.

State: MrtdUiModel

FieldTypeMeaning
phaseMrtdPhaseWhere the read is.
dataGroupslist of stringsThe names of the files read, once phase is Complete.
totalBytes64-bit integerThe total size of the files read, in bytes.
errorMrtdUiError, optionalWhy the read failed, once phase is Failed.

totalBytes is UInt64 in Swift, Long in Kotlin and bigint in TypeScript.

PhaseMeaningSwiftKotlinTypeScript type
IdleNo read has started, or the store was reset..idleMrtdPhase.IdleIDLE
ReadingThe NFC session is open or the chip is being read..readingMrtdPhase.ReadingREADING
CompleteThe chip was read..completeMrtdPhase.CompleteCOMPLETE
CancelledThe read was cancelled with Cancel..cancelledMrtdPhase.CancelledCANCELLED
FailedThe read failed. error says why..failedMrtdPhase.FailedFAILED

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.

ErrorSwiftKotlinTypeScript typeCause
InvalidMrz.invalidMrz(message:)MrtdUiError.InvalidMrzINVALID_MRZThe key data is not valid: a date is neither YYMMDD nor YYYY-MM-DD, or the document number is empty.
ConnectionLost.connectionLost(message:)MrtdUiError.ConnectionLostCONNECTION_LOSTThe connection to the chip broke, or the NFC session was cancelled, not found or not available.
Authentication.authentication(message:)MrtdUiError.AuthenticationAUTHENTICATIONThe 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.ChipNotSupportedCHIP_NOT_SUPPORTEDThe 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.ReadFailedREAD_FAILEDNFC 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.InternalINTERNALAny other failure, for example of the secure connection, a certificate or the cryptography.

The message for each cause, in English:

CauseErrormessage
The device has no NFC reader, or the SDK has no NFC access on the platformChipNotSupportedThis device can’t read document chips. Use a phone with NFC.
The device has an NFC reader, but it is switched offReadFailedNFC is turned off. Turn it on in your device settings and try again.
The chip does not support what the read needsChipNotSupportedThis document chip is not supported.
The key data is not valid, or an authentication step failedInvalidMrz, AuthenticationChip authentication failed. Check the document details and try again.
The connection to the chip brokeConnectionLostConnection lost. Hold your phone near the document again.
Any other failureReadFailed, InternalCouldn’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 ChipNotSupported and 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, with ReadFailed and 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.

StateFieldsWhat to show
Infotitle, description, buttons, optional illustration and processingAn introduction to the chip reading.
Detailsoptional title and description, content (screen containers), optional processingDetails as a screen.
Scantitle, description, documentType, optional hint, buttons, optional keyData, required, optional illustration and processingThe scan screen that reads the chip.
Errora VerificationFailureViewWhy 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:

  1. When the person taps the button whose intent is Submit, keep that intent and do not send it yet.
  2. If keyData lacks the document number, the date of birth or the date of expiry, show the chrome.nfc.missingKey text of the inquiry state and stop.
  3. Send Reset, then Start with the key data, the standard selection and the document type that matches documentType, an NfcDocumentType: MrtdDocumentType Passport for Passport and IdCard for IdCard. Match both cases; there is no other.
  4. When phase becomes Complete, send the kept Submit intent to the InquiryStore. The inquiry then uploads the files and Folio checks the chip data.
  5. When phase becomes Failed, show the error's message, the text the SDK chose for the failure (see Errors). The ready-made UI falls back to the chrome.nfc text for the case when the message is empty: authenticationError for InvalidMrz and Authentication, connectionLost for ConnectionLost, chipNotSupported for ChipNotSupported, and readFailed for ReadFailed and Internal.

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.

On this page