NFC Component

App Components

Use the NFC Component to read the chip in a supported identity document on an existing workflow session.

The NFC Component provides the interface for scanning an identity document's NFC chip. Your app manages the preceding screens, including instructions, CAN entry, and document preparation. It must also collect or obtain the data required to unlock the chip and pass the security key through NfcComponentConfig.

NFC Component

Note

The NFC Component is supported only by the native Android and iOS SDKs. It is not available in the Cordova, Flutter, or React Native plugins.

For more information, see: Document NFC and Supported NFC documents.


Security key

The NFC Component requires one security key to unlock the document chip. Choose the key based on the document and the data available to your app. Pass exactly one key in NfcComponentConfig.

Security keyDescription
MRZ dataDocument number, date of birth, and expiry date from a passport, ID card, residence permit, or French ID card.
CANCard access number collected by your app before launching the scanner.
IDL MRZRaw MRZ data from a Dutch driving licence.

Android

// MRZ data
NfcScannerSecurityKey.createWithMrtdData(
    documentNumber, birthDate, expiryDate
)

// Card access number (CAN)
NfcScannerSecurityKey.createWithMrtdCanNumber(number)

// Dutch driving licence MRZ
NfcScannerSecurityKey.createWithIdlMrz(mrz)

iOS

// MRZ data
NFCScannerSecurityKey.createWithMrtdData(
    documentNumber: documentNumber,
    birthDate: birthDate,
    expiryDate: expiryDate
)

// Card access number (CAN)
NFCScannerSecurityKey.createWithCanNumber(number: number)

// Dutch driving licence MRZ
NFCScannerSecurityKey.createWithIdlMrz(mrz: mrz)

Note

  • Use UTC for all dates.
  • issuingCountry is optional. If provided, use an ISO 3166-1 alpha-3 country code. The SDK uses this value to determine the chip position and configure the scanner.

Document types

The NFC Component supports the following document types:

Document typeDescription
passportPassport
idCardID card
frenchIdCardFrench ID card
residencePermitResidence permit
dutchDriversLicenseDutch driving licence

Before you begin

  1. Start a workflow session (Orca.workflowSession), then call nfcComponent with NfcComponentConfig.
  2. The device must support NFC. On iOS, enable Near Field Communication Tag Reading and the Info.plist keys described in iOS Drop-in setup.
  3. Build the unlock key from data you already have (MRZ fields, a CAN, or an IDL MRZ). The component does not capture those values.
  4. The component runs preparation before the scanner opens. If preparation fails, onProductCompleted runs with a failure and onPresentationStarted is not called.
  5. If documentType is not an NFC document, the component fails with nfc.invalid_configuration before the scanner opens. On a rooted Android device, it fails with nfc.not_available before the scanner opens.

UI customization

Optional visual customization uses OrcaFlavor through NfcCustomizationConfig. See App UI Customization.


Success handling

On success, the completion callback returns NfcComponentResult. The chip payload is scannerResult (NfcScannerResult on Android, NFCScannerResult on iOS): data groups read from the chip, scan metadata (timestamp and location when available), and authentication check results.

Upload that payload with your workflow. Do not treat the SDK callback as the final case status. After upload, use Get workflow status.


Presentation callbacks

present exposes two callbacks:

CallbackWhen it is called
onPresentationStartedPreparation succeeded and the SDK is about to show the scanner. Hide any host loading indicator here. Not called when preparation or configuration checks fail.
onProductCompletedThe component finishes once (success, user cancel, or failure). Always handle the result here, and hide the loading indicator if it is still visible.

Error handling

The NFC Component returns WorkflowFailure when a scan fails.

NFC-specific failure codes use the nfc.* prefix. Shared failure codes, such as session.*, transport.*, and user.canceled, also apply to other components.

Recovery actions

Use the recovery value to determine the next action. For available recovery actions, see Configuration → Error handling → WorkflowFailure.

Recovery actionFailure codesWhat to do
Retrynfc.timeout, nfc.connection_lost, nfc.authentication_failedKeep the same WorkflowSession and call present again.
Unrecoverablenfc.not_available, nfc.invalid_configuration, user.canceledStop the component flow. Handle the outcome based on the failure code.

Failure codes (nfc.*)

CodeDescription
nfc.timeoutThe NFC scan timed out before the chip could be read.
nfc.connection_lostThe connection to the document chip was lost.
nfc.authentication_failedThe unlock key did not match the document chip.
nfc.not_availableNFC is unavailable on this device, or the device is rooted (Android).
nfc.invalid_configurationThe document type cannot be read over NFC.
nfc.unexpectedAn unexpected NFC error occurred.
user.canceledThe client canceled or skipped the scan.

Testing

Start a TestMe session with a mock validation code, then launch the NFC Component with an MRZ unlock key:

import com.fourthline.core.DocumentType
import com.fourthline.networking.NetworkEnvironment
import com.fourthline.nfc.NfcScannerSecurityKey
import com.fourthline.orca.Orca
import com.fourthline.orca.core.flavor.OrcaFlavor
import com.fourthline.orca.nfc.NfcCustomizationConfig
import com.fourthline.orca.workflow.NfcComponentConfig
import com.fourthline.orca.workflow.WorkflowConfig
import com.fourthline.orca.workflow.WorkflowFailure
import com.fourthline.orca.workflow.WorkflowSession
import com.fourthline.orca.workflow.workflowSession
import java.util.Date

fun startSession(context: Context) {
  val config = WorkflowConfig(networkEnvironment = NetworkEnvironment.Mock)

  Orca
    .workflowSession(
      context = context,
      validationCode = "IDV",
    )
    .configure(config)
    .start { result ->
      result.fold(
        onSuccess = { session ->
          launchNfcComponent(context, session)
        },
        onFailure = { workflowError ->
          print("Handle error... $workflowError")
        }
      )
    }
}

fun launchNfcComponent(context: Context, session: WorkflowSession) {
  val securityKey = NfcScannerSecurityKey.createWithMrtdData(
    documentNumber = "SPECI2021",
    birthDate = Date(), // UTC date of birth from the MRZ
    expiryDate = Date(), // UTC expiry date from the MRZ
  )
  val nfcConfig = NfcComponentConfig(
    securityKey = securityKey,
    documentType = DocumentType.PASSPORT,
    issuingCountry = "NLD",
  )

  session
    .nfcComponent(context, nfcConfig)
    .customize(NfcCustomizationConfig(OrcaFlavor()))
    .present(
      onPresentationStarted = {
        // Hide the host loading indicator; the scanner is about to be shown
      },
      onProductCompleted = { result ->
        result.fold(
          onSuccess = { nfcResult ->
            print("Upload NFC result... ${nfcResult.scannerResult}")
          },
          onFailure = { throwable ->
            val failure = throwable as? WorkflowFailure
            print("Handle error... ${failure?.code?.identifier} ${failure?.recovery}")
          },
        )
      },
    )
}
import FourthlineSDK

override func viewDidLoad() {
  super.viewDidLoad()

  let configuration = WorkflowConfig(
    networkEnvironment: .mock
  )
  Orca
  .workflowSession(validationCode: "IDV")
  .configure(with: configuration)
  .start { [weak self] result in
    switch result {
    case let .success(session):
      self?.launchNfcComponent(session)
    case let .failure(workflowError):
      print("Handle error...\(workflowError)")
    }
  }
}

func launchNfcComponent(_ session: WorkflowSession) {
  let securityKey = NFCScannerSecurityKey.createWithMrtdData(
    documentNumber: "SPECI2021",
    birthDate: Date(), // UTC date of birth from the MRZ
    expiryDate: Date() // UTC expiry date from the MRZ
  )
  let nfcConfig = NfcComponentConfig(
    securityKey: securityKey,
    documentType: .passport,
    issuingCountry: "NLD"
  )

  session
    .nfcComponent(with: nfcConfig)
    .customize(with: NfcCustomizationConfig(flavor: OrcaFlavor()))
    .present(
      onPresentationStarted: {
        // Hide the host loading indicator; the scanner is about to be shown
      },
      onProductCompleted: { result in
        switch result {
        case let .success(nfcResult):
          print("Upload NFC result...\(nfcResult.scannerResult)")
        case let .failure(failure):
          print("Handle error...\(failure.code.rawValue) \(failure.recovery)")
        }
      }
    )
}

Example

The following examples use a sandbox validation code and a custom flavor:

import com.fourthline.core.DocumentType
import com.fourthline.networking.NetworkEnvironment
import com.fourthline.nfc.NfcScannerSecurityKey
import com.fourthline.orca.Orca
import com.fourthline.orca.core.flavor.OrcaFlavor
import com.fourthline.orca.core.flavor.OrcaFonts
import com.fourthline.orca.nfc.NfcCustomizationConfig
import com.fourthline.orca.workflow.NfcComponentConfig
import com.fourthline.orca.workflow.WorkflowConfig
import com.fourthline.orca.workflow.WorkflowFailure
import com.fourthline.orca.workflow.WorkflowSession
import com.fourthline.orca.workflow.workflowSession
import java.io.File
import java.util.Date

fun startSession(context: Context) {
  val validationCode = "xxxxxxxx" // generated by calling POST https://{{baseUrl}}/v1/workflows/{{workflowId}}/validationcode
  val config = WorkflowConfig(networkEnvironment = NetworkEnvironment.Sandbox)

  Orca
    .workflowSession(
      context = context,
      validationCode = validationCode,
    )
    .configure(config)
    .start { result ->
      result.fold(
        onSuccess = { session ->
          launchNfcComponent(context, session)
        },
        onFailure = { workflowError ->
          print("Handle error... $workflowError")
        }
      )
    }
}

fun launchNfcComponent(context: Context, session: WorkflowSession) {
  val flavor = OrcaFlavor(
    fonts = OrcaFonts(
      screenHeader = OrcaFonts.Font.FromFontRes(fontRes = R.font.roboto_medium, size = 20),
      primaryButton = OrcaFonts.Font.FromFile(file = File(...), size = 18),
    ),
  )
  val securityKey = NfcScannerSecurityKey.createWithMrtdCanNumber("123456")
  val nfcConfig = NfcComponentConfig(
    securityKey = securityKey,
    documentType = DocumentType.ID_CARD,
    issuingCountry = "NLD",
  )

  session
    .nfcComponent(context, nfcConfig)
    .customize(NfcCustomizationConfig(flavor))
    .present(
      onPresentationStarted = {
        // Hide the host loading indicator; the scanner is about to be shown
      },
      onProductCompleted = { result ->
        result.fold(
          onSuccess = { nfcResult ->
            print("Upload NFC result... ${nfcResult.scannerResult}")
          },
          onFailure = { throwable ->
            val failure = throwable as? WorkflowFailure
            print("Handle error... ${failure?.code?.identifier} ${failure?.recovery}")
          },
        )
      },
    )
}
import FourthlineSDK

func launchNfcComponent(_ session: WorkflowSession) {
  var orcaFlavor = OrcaFlavor()
  var palette = OrcaPalette()
  palette.primary = UIColor(red: 82.0 / 255.0, green: 30.0 / 255.0, blue: 135.0 / 255.0, alpha: 1)
  orcaFlavor.colors = OrcaColors(colorPalette: palette)

  let securityKey = NFCScannerSecurityKey.createWithCanNumber(number: "123456")
  let nfcConfig = NfcComponentConfig(
    securityKey: securityKey,
    documentType: .idCard,
    issuingCountry: "NLD"
  )

  session
    .nfcComponent(with: nfcConfig)
    .customize(with: NfcCustomizationConfig(flavor: orcaFlavor))
    .present(
      onPresentationStarted: {
        // Hide the host loading indicator; the scanner is about to be shown
      },
      onProductCompleted: { result in
        switch result {
        case .success:
          print("Upload NFC result...")
        case let .failure(failure):
          print("Handle error...\(failure.code.rawValue) \(failure.recovery)")
        }
      }
    )
}