1.119.1 NFC / Near-Field Communication Libraries#


Explainer

NFC / Near-Field Communication — Domain Explainer#

What Is NFC?#

NFC (Near-Field Communication) is a short-range wireless technology that lets two devices exchange data when they are within about 4 centimeters of each other. It operates at 13.56 MHz — the same frequency as contactless credit cards and transit cards. You use NFC every time you tap to pay with a phone or transit card.

The “near-field” in the name is physics: the communication happens within the magnetic near-field of an antenna, not as a propagating radio wave. This gives NFC its defining characteristic: it only works when you’re very close. That proximity requirement is both a constraint and a feature — the short range makes accidental triggering unlikely, and a physical tap is a natural user gesture that signals intent.


Two Kinds of NFC Participants#

NFC involves two roles: reader and tag (or card).

Passive tags have no battery. They contain a tiny antenna coil and a chip. When a powered reader (your phone) comes close, the reader’s RF field induces a current in the tag’s coil, powering the chip just enough to transmit data back. This is why NFC tags can last decades — they have no battery to die. A sticker on a product or a wristband at a concert is typically a passive NFC tag.

Active devices (phones, readers, card terminals) power their own antennas. They can both send and receive. An NFC reader in a store, your iPhone, or an Android phone are all active devices.

The critical asymmetry: active devices can read passive tags, but not every active device can act as a passive card. iOS devices (iPhones) are always readers — they can read NFC tags, but they cannot pretend to be a card for another reader to read (with narrow exceptions introduced in 2024 that require special Apple approval). Android devices can do both.


What Lives on a Tag#

Most NFC tags store data using NDEF (NFC Data Exchange Format), the standard message format defined by the NFC Forum (the industry body that governs NFC standards).

An NDEF message contains one or more records. Each record has a type that indicates what kind of data it holds:

  • URL record: A web address. When you tap an NFC-enabled product label, your phone reads a URL and opens it in the browser.
  • Text record: Plain text with a language code.
  • MIME record: Any media type (JSON, image, etc.).
  • External type record: Custom data defined by an application. The type is a string like com.mycompany:deviceconfig.

NDEF is the lingua franca of NFC. If a tag is NDEF-formatted, any NFC-capable phone can read its records — even if the data inside is app-specific.


Smart Card Protocols: ISO 7816 and APDU#

Some NFC interactions go beyond “read a message from a tag” into full protocol exchanges. Credit cards, transit cards, and government IDs use a protocol called ISO 7816, which defines a command-response format called APDU (Application Protocol Data Unit).

An APDU command is a short byte sequence:

[CLA] [INS] [P1] [P2] [data length] [data bytes] [expected response length]

The reader sends a command; the card responds. A real interaction starts with a SELECT AID command that tells the card which application to activate. (AID = Application Identifier, a 5–16 byte name for the app.) Then the reader and card exchange commands and responses.

This is how your phone reads your credit card at a payment terminal, how a transit gate reads your transit card, and how a custom NFC device might exchange encrypted data with another device. The underlying NFC radio is just the transport layer; ISO 7816 is the application layer running on top.


Phone-as-Card: HCE#

Host Card Emulation (HCE) allows an Android phone to act like a smart card — responding to APDU commands from an NFC reader as if the phone were a contactless card.

Before HCE, card emulation required a physical Secure Element (SE) chip in the phone, controlled by the carrier or phone manufacturer. HCE moved the APDU handling into regular app code, making it accessible to any developer. Google introduced HCE in Android 4.4 (2013). Google Pay uses HCE.

To implement HCE: subclass HostApduService, declare which AID your app handles in XML, and implement processCommandApdu() to return response bytes. The Android NFC stack routes incoming APDUs to your service based on the AID.

iOS gained a limited form of HCE in 2024 (iOS 17.4 in Europe, iOS 18.1 globally), but it requires a separate approval process from Apple and is limited to payment, transit, and identity use cases. For general custom applications, Android is the only option for phone-as-card behavior.


MIFARE: The Dominant Tag Family#

MIFARE is a family of NFC chips from NXP Semiconductors. It is the most widely deployed contactless technology in the world — billions of transit cards, access badges, and loyalty cards use it.

The family spans a security spectrum:

MIFARE Classic (ancient, broken): Uses a proprietary cipher called Crypto-1. Crypto-1 was reverse-engineered by security researchers in 2008, and multiple attacks can recover its keys in minutes or seconds using consumer hardware. Transit systems built on MIFARE Classic have been cloned and abused in published research. Do not use MIFARE Classic for any new security application.

MIFARE Ultralight / NTAG (simple, cheap): No authentication, just read/write of small page-based memory. Appropriate for URL tags, configuration stickers, event wristbands where the content is not secret. NTAGs (NXP’s branded version, NTAG213/215/216) are the most common retail NFC stickers.

MIFARE DESFire EV2/EV3 (modern, secure): Uses AES-128 encryption, mutual authentication, hardware-backed key storage. The correct choice for access control, loyalty programs, and any application where cloning must be prevented. Considerably more expensive than Ultralight but genuinely secure.


Cross-Platform Libraries#

For mobile developers, three paths exist:

React Native: react-native-nfc-manager is the de facto standard. It provides a JavaScript API over CoreNFC (iOS) and NfcAdapter (Android), covering NDEF read/write, ISO 7816 APDU exchanges, and MIFARE Ultralight. HCE requires native Android code — it cannot be driven from JavaScript because HostApduService.processCommandApdu() must return synchronously.

Flutter: nfc_manager is the primary library (160/160 pub points, ~522 likes). flutter_nfc_kit is the alternative, notable for being the only Flutter library that also supports Web NFC (Android Chrome). For new projects without web requirements, nfc_manager is preferred.

Web: The NDEFReader API (Web NFC) allows browser pages on Android Chrome to read and write NDEF tags without an app install. It is NDEF-only — no APDU access, no card emulation, no MIFARE protocol. iOS Safari does not implement Web NFC and has no announced plans to do so.


Physical Reality#

NFC operates at 13.56 MHz in the globally unlicensed ISM band. The usable range for a phone-to-tag interaction is typically 0–4 cm; phone-to-phone is 0–10 cm. Data rate is 106–424 kbps (fast enough for typical exchanges; not fast enough for large file transfers).

Timing for common operations:

  • Tag detected: 50–200 ms after bringing phone near
  • NDEF read (small tag): 100–300 ms total
  • APDU exchange (3–5 commands): 50–200 ms after tag detection

A user must hold their phone near the tag for the duration of the exchange. For simple reads this is imperceptible. For multi-step protocols (key exchange, authentication), the user needs to maintain proximity for roughly half a second — design the protocol to minimize round trips.

NFC has no physical-layer encryption. Eavesdropping with a sensitive antenna from ~1 meter is possible for device-to-device exchanges. Any sensitive exchange should use application-layer encryption.


The Asymmetry Principle#

NFC is always reader + card, never peer-to-peer.

Every NFC interaction has one party polling for tags (reader) and one party responding (card). This asymmetry shapes everything:

  • iOS-to-iOS NFC communication is impossible (both are readers)
  • iOS reading Android HCE works perfectly (iOS is reader, Android is card)
  • Android-to-Android is possible but one must be in HCE mode

If you need symmetric peer-to-peer proximity communication, BLE (Multipeer Connectivity on iOS, Nearby Connections on Android) or Wi-Fi Aware are the right tools. NFC excels for intentional tap interactions, not P2P sessions.

S1: Rapid Discovery

S1 Approach: NFC / Near-Field Communication Libraries#

Research ID: 1.119.1 Pass: S1 — Rapid Discovery Date: 2026-02-17

Scope#

NFC for mobile app development: reading tags, writing tags, card emulation, and device-to-device communication. Key context: YIM protocol uses NFC as the physical trust bootstrap — two phones tap to initiate a key exchange. This is the lowest-level trust anchor in the entire protocol.

Key Questions#

  1. What are the platform-native APIs? (iOS CoreNFC, Android NfcAdapter + HCE)
  2. What cross-platform libraries exist? (React Native: react-native-nfc-manager; Flutter: nfc_manager)
  3. What is actually possible for device-to-device NFC? (Android HCE emulates a card; iOS can only read — no P2P between two iPhones)
  4. Web NFC: scope and limitations (Chrome Android only, NDEF only)
  5. Physical characteristics: range, speed, security considerations

Search Strategy#

  • Apple docs: CoreNFC, NFCNDEFReaderSession, NFCTagReaderSession, entitlement requirements
  • Android docs: NfcAdapter, HostApduService (HCE), foreground dispatch
  • npm: react-native-nfc-manager (stars, downloads, version)
  • pub.dev: nfc_manager, flutter_nfc_kit
  • W3C: Web NFC spec status

S1 Overview: NFC / Near-Field Communication Libraries#

What NFC Is#

Near-Field Communication (NFC) is a short-range wireless standard operating at 13.56 MHz. It enables communication between devices within ~4 cm (passive tags) or ~20 cm (active device-to-device). NFC is a subset of RFID — it uses the same frequency as ISO 14443 contactless smart cards and MIFARE tags.

Three operational modes:

  • Reader/Writer: Phone reads or writes an NFC tag
  • Card Emulation: Phone behaves like a contactless card (e.g., for payment)
  • Peer-to-Peer (P2P): Two NFC-capable devices exchange data (largely deprecated)

Platform APIs#

iOS: CoreNFC#

CoreNFC is Apple’s framework for NFC. Available since iOS 11 (read-only NDEF), expanded in iOS 13 (write, non-NDEF tag types), iOS 14 (background tag reading).

Two session types:

// 1. NDEF session — reads/writes NDEF-formatted tags
// Simple: NFCNDEFReaderSession
let session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: true)
session.alertMessage = "Hold your iPhone near the NFC tag."
session.begin()

// Delegate callback
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
    for message in messages {
        for record in message.records {
            // record.typeNameFormat, record.type, record.payload
        }
    }
}
// 2. Tag reader session — for ISO 7816, MIFARE, FeliCa, ISO 15693
// More powerful: NFCTagReaderSession
let session = NFCTagReaderSession(pollingOption: [.iso14443, .iso15693, .iso18092], delegate: self)
session.begin()

func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
    let tag = tags.first!
    session.connect(to: tag) { error in
        if case .iso7816(_) = tag, let isoTag = try? tag.asNFCISO7816Tag() {
            // Send APDU commands
            let selectAID = NFCISO7816APDU(
                instructionClass: 0x00,
                instructionCode: 0xA4,
                p1Parameter: 0x04,
                p2Parameter: 0x00,
                data: Data([0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01]),
                expectedResponseLength: -1
            )
            isoTag.sendCommand(apdu: selectAID) { data, sw1, sw2, error in
                // sw1=0x90, sw2=0x00 means success
            }
        }
    }
}

Supported tag types (iOS 13+):

  • iso7816: ISO 14443-4 (APDU commands via NFCISO7816Tag)
  • miFare: MIFARE Classic, MIFARE Ultralight, MIFARE Plus
  • feliCa: Sony FeliCa (popular in Japan)
  • iso15693: Vicinity cards (ISO 15693)

Write support (iOS 13+):

// On NFCNDEFTag (from NFCTagReaderSession or NFCNDEFReaderSession)
tag.writeNDEF(message) { error in /* check error */ }

Background tag reading (iOS 14+): iPhone reads NFC tags in the background without opening the app. URL records in NDEF messages can deep-link into the app. Requires entitlement and a URL matching the app’s associated domains.

Critical limitation: iOS cannot emulate an NFC card. There is no Host Card Emulation (HCE) API on iOS. Apple Pay uses the Secure Element for card emulation, but third-party apps cannot access it for custom protocols.

Entitlement required: com.apple.developer.nfc.readersession.formats must be in the app’s entitlement file. Available to all registered Apple developers (no special approval required, unlike some capabilities).

Simulator: CoreNFC does not work in the iOS Simulator. Physical iPhone required.


Android: NFC Stack#

Android has supported NFC since API 9 (Android 2.3). Key classes: NfcAdapter, NfcManager, NdefMessage, NdefRecord.

Three dispatch mechanisms:

// 1. Intent-based dispatch (default) — app receives Intent when tag detected
// Requires android.permission.NFC in manifest
// In onNewIntent:
val tag: Tag? = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
val ndef = Ndef.get(tag)
ndef?.connect()
val message = ndef?.ndefMessage
// 2. Foreground dispatch — app gets priority over other apps while in foreground
val adapter = NfcAdapter.getDefaultAdapter(this)
val pendingIntent = PendingIntent.getActivity(this, 0, Intent(this, javaClass), PendingIntent.FLAG_MUTABLE)
// In onResume:
adapter.enableForegroundDispatch(this, pendingIntent, null, null)
// In onPause:
adapter.disableForegroundDispatch(this)
// 3. Reader Mode (preferred since API 19) — more reliable, less overhead
adapter.enableReaderMode(this, { tag ->
    // Called on background thread when tag detected
    val isoDep = IsoDep.get(tag)
    isoDep?.connect()
    val response = isoDep?.transceive(byteArrayOf(0x00, 0xA4.toByte(), 0x04, 0x00, 7,
        0xD2.toByte(), 0x76, 0x00, 0x00, 0x85.toByte(), 0x01, 0x01, 0x00))
    // Parse response bytes
}, NfcAdapter.FLAG_READER_ISO_DEP or NfcAdapter.FLAG_READER_NFC_A, null)

Tag technology classes:

  • IsoDep: ISO 14443-4 (contact/contactless smart cards, APDU commands)
  • Ndef/NdefFormatable: Read/write NDEF messages
  • MifareClassic: MIFARE Classic tags (sectors, blocks, authentication)
  • MifareUltralight: MIFARE Ultralight/C (simpler, common for URL tags)
  • NfcA/NfcB/NfcF/NfcV: Low-level access to tag protocols

Host Card Emulation (HCE) (API 19+):

// HostApduService subclass — Android routes contactless commands to this service
class MyHCEService : HostApduService() {
    override fun processCommandApdu(commandApdu: ByteArray, extras: Bundle?): ByteArray {
        // commandApdu is the APDU from the reader
        return if (commandApdu.contentEquals(SELECT_APDU)) {
            SUCCESS_RESPONSE  // e.g., byteArrayOf(0x90, 0x00)
        } else {
            UNKNOWN_CMD_RESPONSE
        }
    }
    override fun onDeactivated(reason: Int) { /* reader moved away */ }
}

HCE requires: android.permission.NFC, service declared in manifest with <intent-filter> for HOST_APDU_SERVICE, AID group registration.

Android Beam removal: Android Beam (P2P NFC data transfer) was removed in Android 10 (API 29). Replaced by Nearby Share for general device-to-device file transfer. No built-in P2P NFC replacement for raw data exchange — developers must implement HCE on one device + reader mode on the other.


Device-to-Device NFC#

ScenarioPossible?Mechanism
Android → Android (bidirectional)✅ YesDevice A: HCE (card emulator). Device B: reader mode. Then swap roles.
iOS → Android HCE✅ YesiOS reads ISO 7816 APDU from Android HCE service
Android → iOS❌ NoiOS cannot run HCE. Android reader reads nothing from iPhone.
iOS → iOS❌ NoNeither phone can emulate a card

iOS-to-iOS P2P is not possible via NFC. Both iPhones are reader-only. AirDrop is the Apple-native substitute for device-to-device data exchange between iPhones.

Android bidirectional pattern:

Phone A: enableReaderMode()          → reads Phone B's HCE
Phone B: HostApduService running     → responds to A's APDU commands
         (then reverse roles)
Phone A: HostApduService running     → responds to B's APDU commands
Phone B: enableReaderMode()          → reads Phone A's HCE

Both roles can be active simultaneously on Android — one app can run a foreground enableReaderMode() while the OS still routes incoming contactless requests to the registered HostApduService.


Cross-Platform Libraries#

react-native-nfc-manager#

GitHub: revtel/react-native-nfc-manager | ~1,600 stars | npm: react-native-nfc-manager | ~33K downloads/week (v3.17.2) Version: 3.14.x (2025) | Language: JS + native (Swift/Kotlin)

import NfcManager, { NfcTech, Ndef } from 'react-native-nfc-manager';

// Initialize
await NfcManager.start();

// Read NDEF tag
async function readNfcTag() {
  await NfcManager.requestTechnology(NfcTech.Ndef);
  const tag = await NfcManager.getTag();
  const message = tag.ndefMessage;
  // message[0].payload = Ndef.decodeMessage(...)
  NfcManager.cancelTechnologyRequest();
}

// Write NDEF tag
async function writeNfcTag(text) {
  await NfcManager.requestTechnology(NfcTech.Ndef);
  const bytes = Ndef.encodeMessage([Ndef.textRecord(text)]);
  await NfcManager.ndefHandler.writeNdefMessage(bytes);
  NfcManager.cancelTechnologyRequest();
}

// ISO 7816 APDU (requires IsoDep on Android, iso7816 on iOS)
async function sendApdu() {
  await NfcManager.requestTechnology(NfcTech.IsoDep);  // Android
  // or NfcTech.Iso7816 on iOS
  const response = await NfcManager.isoDepHandler.transceive([
    0x00, 0xA4, 0x04, 0x00, 0x07,  // SELECT AID
    0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01
  ]);
  NfcManager.cancelTechnologyRequest();
}

Platform coverage:

  • iOS: NDEF read/write, ISO 7816 APDU, FeliCa, ISO 15693
  • Android: NDEF, ISO 7816, MIFARE Classic/Ultralight, HCE (via Android native — not directly exposed in JS API, requires native module extension)

nfc_manager (Flutter)#

pub.dev: nfc_manager | ~160 pub points | version: 3.5.0 Downloads: ~15K/month

import 'package:nfc_manager/nfc_manager.dart';

// Check availability
bool isAvailable = await NfcManager.instance.isAvailable();

// Start session
NfcManager.instance.startSession(
  onDiscovered: (NfcTag tag) async {
    // Read NDEF
    final ndef = Ndef.from(tag);
    if (ndef != null) {
      final message = await ndef.read();
      // message.records
    }

    // ISO 7816 (Android IsoDep / iOS NFCISO7816Tag)
    final isoDep = IsoDep.from(tag);
    if (isoDep != null) {
      final response = await isoDep.transceive(
        data: Uint8List.fromList([0x00, 0xA4, 0x04, 0x00]),
      );
    }
  },
);

// Stop
NfcManager.instance.stopSession();

flutter_nfc_kit#

pub.dev: flutter_nfc_kit | ~130 pub points | version: 3.5.0 Downloads: ~8K/month

Alternative Flutter library. Similar API surface. Slightly less maintained than nfc_manager. No strong reason to prefer over nfc_manager for new projects.


Web NFC#

W3C Status: Candidate Recommendation (CR) as of 2023. Browser support: Chrome for Android only (Chrome 89+). No iOS Safari. No desktop.

// NDEFReader — NDEF read only
const reader = new NDEFReader();
await reader.scan();
reader.addEventListener("reading", ({ message, serialNumber }) => {
  for (const record of message.records) {
    if (record.recordType === "text") {
      const decoder = new TextDecoder();
      console.log(decoder.decode(record.data));
    }
  }
});

// Write
const writer = new NDEFReader();
await writer.write({ records: [{ recordType: "text", data: "hello" }] });

Limitations: NDEF only (no APDU, no raw protocol access), Android Chrome only, requires user gesture to initiate scan, no card emulation.


Physical Characteristics#

PropertyValue
Frequency13.56 MHz
Operating range (passive tag)0–4 cm typical
Operating range (device-to-device)Up to ~10–20 cm
NDEF read time~100–300 ms
NDEF write time~200–500 ms
APDU round-trip~50–150 ms per command
Eavesdropping rangeUp to ~1 m with sensitive equipment
Data rate106–848 kbps (ISO 14443)

Security considerations:

  • Eavesdropping: practical range ~10–30 cm for passive tags, can extend to ~1 m with specialized equipment
  • Relay attacks: NFC signals relayed over longer distances (NFC payment relay attacks)
  • Jamming: 13.56 MHz jammers can block NFC (illegal in most jurisdictions)
  • Physical proximity requirement is the primary security property — proximity implies physical presence

Quick Verdict Table#

ToolPlatformWriteAPDUHCEVerdict
iOS CoreNFCiOS✅ (iOS 13+)Standard for iOS
Android NFCAndroid✅ (API 19+)Standard for Android
react-native-nfc-managerReact Native⚠️ partialBest RN option
nfc_managerFlutterBest Flutter option
flutter_nfc_kitFlutterAlternative
Web NFCChrome AndroidLimited scope

S1 Recommendation: NFC / Near-Field Communication Libraries#

Preliminary Winners#

Use CaseRecommendation
iOS native: read NDEF tagsCoreNFC NFCNDEFReaderSession
iOS native: ISO 7816 APDU, MIFARE, FeliCaCoreNFC NFCTagReaderSession
Android native: read tagsNfcAdapter + Reader Mode (API 19+)
Android native: card emulationHostApduService (HCE, API 19+)
React Native: any NFCreact-native-nfc-manager
Flutter: any NFCnfc_manager
Web: simple NDEF on Android ChromeWeb NFC NDEFReader

Critical Decisions#

Cross-platform bidirectional NFC: Not possible with iOS on both sides. For Android-Android: use HCE on one device and reader mode on the other. For iOS-Android: iOS reads from Android HCE.

Simple tag reading vs custom protocol: NDEF is sufficient for URL/text/vCard use cases. ISO 7816 APDU is required for any custom exchange protocol (authentication, key material, structured data).

Anti-Patterns#

  • Expecting iOS to emulate a card (not possible — no HCE API)
  • Using Android Beam (removed in Android 10)
  • Using Web NFC for custom protocols (NDEF only, no APDU access)
  • Expecting NFC to work in iOS Simulator (physical device required)

S1 Synthesis: NFC / Near-Field Communication Libraries#

Key Findings#

1. The iOS HCE Limitation Is the Central Constraint#

iOS can only read NFC. It cannot emulate a card. This is the most important architectural fact in this domain. Any protocol requiring two iOS devices to exchange data via NFC is impossible. Android devices can both read and emulate (HCE), enabling bidirectional exchange when one device acts as reader and the other as card.

Consequence: For cross-platform NFC, the common denominator is “one device reads, the other emits.” iOS devices must be the reader. Android devices can be either reader or emitter.

2. Android Beam Is Gone — No Replacement for Raw P2P NFC#

Android Beam (SNEP protocol over NFC P2P) was deprecated in Android 10 (API 29) and fully removed in Android 14 (API 34). Google replaced it with Nearby Share / Quick Share for general file transfer (NFC used only as Bluetooth/Wi-Fi bootstrap tap), but there is no built-in Android API for custom P2P NFC data exchange. Developers who need device-to-device NFC must implement HCE on one device and reader mode on the other.

3. react-native-nfc-manager Is the Clear RN Winner#

~1,600 stars, ~33K downloads/week (v3.17.2), maintained by revtel. Covers all major use cases: NDEF, ISO 7816 APDU, MIFARE on both platforms. HCE is not exposed directly in the JS API (requires native extension) — worth noting.

4. For Flutter: nfc_manager Over flutter_nfc_kit (with One Exception)#

nfc_manager (v4.1.1, 160/160 pub points, ~522 likes) is the preferred choice. However, flutter_nfc_kit (v3.6.0) is the only Flutter library that supports Web NFC (Android Chrome) — choose it when web support is required.

5. Web NFC Is Android-Chrome Only and NDEF Only#

No APDU access, no iOS, no desktop. Useful for web apps that need to read simple NFC tags on Android. Not suitable for anything requiring custom protocols or cross-platform.

6. APDU Commands Are the Power Primitive#

For custom protocols (not just reading URL/text tags), ISO 7816 APDU transceive is the right primitive. Both iOS (NFCISO7816APDU) and Android (IsoDep.transceive) support it. This is how payment terminals and custom smart card protocols work — fully applicable to custom NFC applications.

What S2 Should Investigate#

  • ISO 7816 APDU command structure (CLA INS P1 P2 Lc Data Le)
  • NDEF record types in depth (RTD_TEXT, RTD_URI, RTD_SMART_POSTER, external types)
  • Android HCE AID registration and SELECT AID flow
  • react-native-nfc-manager: exact HCE situation (does it expose any HCE hooks?)
  • MIFARE Classic authentication (sector keys) — security implications
  • NFC polling intervals and discovery timing
S2: Comprehensive

S2 Approach: NFC / Near-Field Communication Libraries#

Research ID: 1.119.1 Pass: S2 — Comprehensive Analysis Date: 2026-02-17

Deep-Dive Scope#

  1. NDEF record format: TNF, type, ID, payload — in depth
  2. ISO 7816 APDU command structure: CLA, INS, P1, P2, Lc, Data, Le
  3. Android HCE deep dive: AID registration, SELECT AID flow, APDU exchange lifecycle, OffHost vs OnHost emulation
  4. iOS CoreNFC internals: session lifecycle, polling loop, tag connection, error handling
  5. react-native-nfc-manager full API: NfcTech enum, handler methods, HCE situation
  6. MIFARE security: sector authentication, key diversification, attack surface
  7. NFC security: eavesdropping, relay attacks, skimming, shielding
  8. Performance: polling intervals, session establishment time, throughput

S2 Comprehensive Analysis: NFC / Near-Field Communication Libraries#

Research ID: 1.119.1 Pass: S2 — Comprehensive Analysis Date: 2026-02-17


1. NDEF: NFC Data Exchange Format#

NDEF is the standard message format for NFC tags and communication. An NDEFMessage contains one or more NDEFRecords.

Record Structure#

Byte 0: Header flags
  - MB (Message Begin): first record
  - ME (Message End): last record
  - CF (Chunk Flag): multi-chunk record
  - SR (Short Record): payload length in 1 byte
  - IL (ID Length present)
  - TNF (Type Name Format): 3 bits

Byte 1: Type Length
Byte 2-n: Payload Length (1 or 4 bytes, SR flag determines)
Byte n+1 (optional): ID Length
Byte n+2: Type
Byte n+3 (optional): ID
Byte n+4: Payload

TNF (Type Name Format) values:

ValueMeaning
0x00Empty
0x01Well-Known Type (RTD)
0x02MIME media type
0x03Absolute URI
0x04External Type (custom)
0x05Unknown
0x06Unchanged (chunked)

Common Record Types#

RTD_TEXT (type = "T", TNF = Well-Known):

Payload: [status byte][language code][text]
  Status byte: 0x02 = UTF-8, length of language code in bits 0-5
  Example: 0x02 "en" "Hello World"

RTD_URI (type = "U", TNF = Well-Known):

Payload: [URI identifier code][URI string]
  0x01 = "http://www."
  0x02 = "https://www."
  0x03 = "http://"
  0x04 = "https://"
  0x00 = no prefix

MIME type (TNF = 0x02):

Type: "application/json", "image/png", etc.
Payload: raw bytes of the MIME content

External type (TNF = 0x04): Custom application-defined records. Format: domain:type

Example type: "com.myapp:keyexchange"
Payload: arbitrary bytes

Writing NDEF on iOS#

// iOS 13+ write
func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
    let tag = tags.first!
    session.connect(to: tag) { error in
        tag.queryNDEFStatus { status, capacity, error in
            guard status == .readWrite else { return }

            // Create a URI record
            let url = URL(string: "https://example.com")!
            let payload = NFCNDEFPayload.wellKnownTypeURIPayload(url: url)!
            let message = NFCNDEFMessage(records: [payload])

            tag.writeNDEF(message) { error in
                session.alertMessage = error == nil ? "Written!" : "Write failed"
                session.invalidate()
            }
        }
    }
}

Writing NDEF on Android#

val tag: Tag = ...
val ndef = Ndef.get(tag) ?: NdefFormatable.get(tag)

// Write to NDEF-formatted tag
val record = NdefRecord.createUri(Uri.parse("https://example.com"))
val message = NdefMessage(arrayOf(record))
(ndef as Ndef).connect()
ndef.writeNdefMessage(message)
ndef.close()

2. ISO 7816 APDU Commands#

ISO 7816 Application Protocol Data Units (APDUs) are the command-response protocol for smart cards and NFC tags implementing the ISO 14443-4 standard (T=CL — contactless).

Command APDU Structure#

CLA  INS  P1  P2  [Lc  Data  Le]
 1    1    1   1   1    n     1   bytes

CLA: Class byte (0x00 for standard commands)
INS: Instruction code (command type)
P1, P2: Parameters (command-specific)
Lc: Length of Data field (omitted if no data)
Data: Command data
Le: Expected response length (0x00 = up to 256 bytes)

Common Commands#

SELECT AID (select application):
  00 A4 04 00 [Lc] [AID bytes] [Le]
  Example AID (NDEF): D2 76 00 00 85 01 01

READ BINARY (read file content):
  00 B0 [P1 P2 = offset] [Le = bytes to read]

VERIFY (PIN):
  00 20 00 [P2 = key ref] [Lc] [PIN data]

GET DATA:
  00 CA [P1] [P2] [Le]

Response APDU Structure#

[Data]  SW1  SW2
  n      1    1   bytes

SW1=0x90, SW2=0x00: Success
SW1=0x61, SW2=n:    More data available (GET RESPONSE for n bytes)
SW1=0x6A, SW2=0x82: File not found
SW1=0x69, SW2=0x82: Security conditions not satisfied
SW1=0x6D, SW2=0x00: Instruction code not supported

APDU on iOS#

// NFCTagReaderSession → NFCTag → NFCISO7816Tag
let apdu = NFCISO7816APDU(
    instructionClass: 0x00,
    instructionCode: 0xB0,   // READ BINARY
    p1Parameter: 0x00,
    p2Parameter: 0x00,
    data: Data(),
    expectedResponseLength: 15  // Le
)
isoTag.sendCommand(apdu: apdu) { responseData, sw1, sw2, error in
    // responseData: Data, sw1/sw2: UInt8
    if sw1 == 0x90 && sw2 == 0x00 {
        // success, process responseData
    }
}

APDU on Android#

val isoDep = IsoDep.get(tag)!!
isoDep.connect()
isoDep.timeout = 3000  // 3 second timeout

// Select AID
val selectCmd = byteArrayOf(
    0x00, 0xA4.toByte(), 0x04, 0x00, 0x07,
    0xD2.toByte(), 0x76, 0x00, 0x00, 0x85.toByte(), 0x01, 0x01,
    0x00
)
val response = isoDep.transceive(selectCmd)
val sw1 = response[response.size - 2]
val sw2 = response[response.size - 1]
// response bytes before SW1/SW2 are the response data
isoDep.close()

3. Android HCE: Deep Dive#

Host Card Emulation (HCE) allows an Android app to respond to NFC reader queries as if the phone were a contactless smart card. Available since API 19 (Android 4.4).

Architecture#

NFC Reader
    ↕ (13.56 MHz)
Android NFC Controller (hardware)
    ↕
Android NFC Stack (system service)
    ↕ (APDU routing via AID)
HostApduService (your app)

OnHost vs OffHost:

  • HostApduService (OnHost): APDUs routed to app code running on the main CPU. Standard HCE.
  • OffHostApduService: APDUs routed to a Secure Element (SE) or UICC (SIM). Used for payment (Google Pay). Requires special permissions / hardware SE.

AID Registration#

Each HCE service declares which Application Identifier (AID) it handles. The OS routes APDU traffic based on AID.

<!-- AndroidManifest.xml -->
<service android:name=".MyHCEService"
         android:exported="true"
         android:permission="android.permission.BIND_NFC_SERVICE">
    <intent-filter>
        <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
    </intent-filter>
    <meta-data android:name="android.nfc.cardemulation.host_apdu_service"
               android:resource="@xml/apduservice"/>
</service>
<!-- res/xml/apduservice.xml -->
<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/service_name"
    android:requireDeviceUnlock="false">
    <aid-group android:description="@string/card_title"
               android:category="other">
        <aid-filter android:name="F0010203040506"/>
        <!-- AID: 7 bytes, hex, must start with non-payment prefix -->
    </aid-group>
</host-apdu-service>

AID categories:

  • payment: For payment applications. Requires Google certification and shows UI to user for default selection.
  • other: Custom applications. No special certification. Multiple apps can claim same AID (OS prompts user to choose).

HCE Service Implementation#

class MyHCEService : HostApduService() {
    companion object {
        val SELECT_AID = hexToBytes("00A4040007F0010203040506 00")
        val OK = byteArrayOf(0x90.toByte(), 0x00)
        val UNKNOWN = byteArrayOf(0x6D, 0x00)
    }

    override fun processCommandApdu(commandApdu: ByteArray, extras: Bundle?): ByteArray {
        return when {
            commandApdu.startsWith(SELECT_AID.take(5).toByteArray()) -> {
                // Application selected — begin protocol
                OK
            }
            commandApdu[1] == 0xB0.toByte() -> {
                // READ BINARY — return data + OK
                myData + OK
            }
            else -> UNKNOWN
        }
    }

    override fun onDeactivated(reason: Int) {
        // reason: DEACTIVATION_LINK_LOSS or DEACTIVATION_DESELECTED
    }
}

HCE Limitations#

  • Requires device unlock by default (configurable via requireDeviceUnlock)
  • Screen must be on — HCE does not work when screen is off (unless using HCE_PAYMENT mode on some devices)
  • Background service: the HostApduService runs as a bound service — keep minimal, avoid long operations in processCommandApdu
  • Single AID winner: only one service handles a given AID at a time (OS prompts on conflict for other category)
  • Android only: no iOS equivalent

4. iOS CoreNFC: Session Lifecycle#

Session States#

NFCTagReaderSession
  ↓ begin()
  → System shows "Ready to Scan" sheet
  ↓ (user holds iPhone near tag)
  → tagReaderSession(_:didDetect:) callback
  ↓ session.connect(to: tag)
  → Tag connected
  ↓ (APDU exchange or NDEF read)
  ↓ session.invalidate() or session.alertMessage + invalidate()
  → Sheet dismissed

Session constraints:

  • Session has a ~20 second timeout from begin(). No built-in way to extend.
  • Only one active session at a time.
  • After invalidation, create a new session for the next scan.
  • invalidateAfterFirstRead: true auto-invalidates after first tag detection (simpler, use for one-shot reads).

Background Tag Reading (iOS 14+)#

Without opening the app, iOS can read NFC tags containing URLs. The URL must match the app’s Associated Domains (universal links). A banner notification appears.

Tag has NDEF record: RTD_URI → "https://myapp.example.com/open?id=123"
→ iOS detects tag
→ Shows "Open in MyApp" banner
→ User taps → app opens, receives the URL

No NFCNDEFReaderSession needed. Happens automatically when screen is on and phone is near a tag.

Limitation: URL records only. Cannot trigger custom APDU exchange in background.


5. react-native-nfc-manager: Full API#

NfcTech enum values (technology types to request):

NfcTech.Ndef            // NDEF read/write (iOS + Android)
NfcTech.NfcA            // ISO 14443-3A (Android only)
NfcTech.NfcB            // ISO 14443-3B (Android only)
NfcTech.NfcF            // ISO 18092 FeliCa (Android only)
NfcTech.NfcV            // ISO 15693 (Android only)
NfcTech.IsoDep          // ISO 14443-4 APDU (Android — maps to NFCTagReaderSession on iOS)
NfcTech.MifareClassic   // MIFARE Classic (Android only)
NfcTech.MifareUltralight // MIFARE Ultralight (Android + iOS via NfcTagReaderSession)
NfcTech.Iso7816         // ISO 7816 APDU (iOS — same as IsoDep on Android)
NfcTech.Ndef_Formatable // Formatting blank tags (Android only)

Handler objects:

// NdefHandler
NfcManager.ndefHandler.writeNdefMessage(bytes)
NfcManager.ndefHandler.getNdefMessage()
NfcManager.ndefHandler.makeReadOnly()

// IsoDepHandler (Android) / Iso7816Handler (iOS)
NfcManager.isoDepHandler.transceive([0x00, 0xA4, ...])
NfcManager.iso7816Handler.sendCommandAPDU({ ... })

// MifareClassicHandler (Android)
NfcManager.mifareClassicHandler.authenticateSectorWithKeyA(sector, key)
NfcManager.mifareClassicHandler.readBlock(block)
NfcManager.mifareClassicHandler.writeBlock(block, data)

// MifareUltralightHandler
NfcManager.mifareUltralightHandler.readPages(pageOffset)
NfcManager.mifareUltralightHandler.writePage(pageOffset, data)

HCE situation: react-native-nfc-manager does not provide a JavaScript API for Android HCE. HCE requires a HostApduService which must be a native Android Service — it cannot be fully driven from JavaScript because it must respond synchronously in the service’s processCommandApdu(). Developers must write the HCE service in native Kotlin/Java and use an event bridge or shared state if they want the JS layer to be involved.


6. NFC Security#

Eavesdropping#

NFC’s short range is the primary security property, but is not absolute:

  • Passive eavesdropping: A sensitive antenna can receive 13.56 MHz transmissions from ~0.5–1 m away (research demonstrations have reached further).
  • Active eavesdropping: Much more difficult — requires intercepting the device response too.
  • Mitigation: Application-layer encryption independent of physical proximity.

Relay Attacks#

An attacker relays NFC communications over a longer distance:

Victim's card → Attacker's reader A → Radio link → Attacker's emulator B → Payment terminal

The transaction appears local but the card is meters or kilometers away.

Countermeasures:

  • Time-of-flight: Measure response latency (relay adds ~100+ ms delay)
  • Location correlation: If transaction time doesn’t match card location
  • Distance bounding protocols: Cryptographic proof of proximity

MIFARE Classic Vulnerabilities#

MIFARE Classic uses the CRYPTO1 cipher, which has multiple published weaknesses:

  • Darkside attack: Recovers sector key from a single authentication failure
  • Nested authentication attack: Recovers unknown keys using one known key
  • Card-only attack: Recovers key without a reader, using only the tag

Avoid MIFARE Classic for security-sensitive applications. Use MIFARE DESFire (AES-based) or ISO 7816 with application-layer cryptography.


7. Performance Characteristics#

OperationTypical Time
NFC field detection + polling50–200 ms
Session establishment100–300 ms
NDEF read (small tag, ~100 bytes)100–300 ms
NDEF write (~100 bytes)200–500 ms
Single APDU round-trip50–150 ms
Multiple APDU exchange (4 commands)400–800 ms
Total: tap-to-result for simple read~300–600 ms
Total: tap-to-result for APDU protocol~600–1500 ms

ISO 14443-4 data rate: 106 kbps (standard), 212/424/848 kbps with RATS negotiation.

The physical tap must be maintained for the duration of the exchange. For multi-APDU protocols, the user needs to hold the device still for ~1 second.


Sources#


S2 Findings: NFC / Near-Field Communication Libraries#

Research ID: 1.119.1 Pass: S2 — Comprehensive Analysis Date: 2026-02-17


1. iOS CoreNFC#

1.1 Session Types: NFCNDEFReaderSession vs NFCTagReaderSession#

NFCNDEFReaderSession (iOS 11+)

The high-level session. Automatically polls, detects, and reads any NFC Forum tag that contains a valid NDEF message. Delivers parsed NFCNDEFMessage objects to the delegate. Does not expose the raw tag object — you cannot send arbitrary commands. Use this when you only need to read NDEF payloads (URLs, plain text, vCards, custom external types).

NFCNDEFReaderSession also supports writing (iOS 13+): connect to the tag, call writeNDEF(_:completionHandler:) on it, and optionally lock the tag with writeLock(completionHandler:).

NFCTagReaderSession (iOS 13+)

The low-level session. Detects tags as typed objects (NFCTag enum) and hands them to the delegate without parsing. You must call session.connect(to:completionHandler:) before communicating. Gives access to raw tag interfaces:

  • NFCISO7816Tag — ISO 7816 APDU transceive
  • NFCMiFareTag — MIFARE Ultralight, Plus, DESFire (NOT Classic)
  • NFCISO15693Tag — ISO 15693 (vicinity cards)
  • NFCFeliCaTag — FeliCa (Sony, used in Japan transit)

Use this session when you need to send APDU commands, read proprietary tag memory, or work with non-NDEF tags.

Decision rule: If you need NDEF read/write → use NFCNDEFReaderSession. If you need raw protocol access, APDU commands, or MIFARE/FeliCa/ISO 15693 communication → use NFCTagReaderSession.

1.2 Supported Tag Types#

Tag TypeSessionNotes
ISO 7816 (IsoDep)NFCTagReaderSessionAPDU transceive via NFCISO7816Tag; AID list required in entitlement
MIFARE UltralightNFCTagReaderSessionSupported via NFCMiFareTag
MIFARE ClassicNOT SUPPORTEDApple never implemented Crypto-1 authentication; hardware can detect the tag family but cannot authenticate sectors
MIFARE PlusNFCTagReaderSessionSupported at SL0/SL1 (like Ultralight); SL3 requires Crypto AES
MIFARE DESFireNFCTagReaderSessionSupported
FeliCaNFCTagReaderSessionVia NFCFeliCaTag; request code + service code model
ISO 15693NFCTagReaderSessionVia NFCISO15693Tag; commonly used in asset tracking
NFC-A/B (generic NDEF)NFCNDEFReaderSessionPolled automatically; NDEF only

MIFARE Classic clarification: The iPhone hardware operates at 13.56 MHz and can detect Classic tags in the polling loop, but CoreNFC deliberately omits the Crypto-1 implementation. The tag type will not be surfaced to app code. This is a software decision, not a hardware limitation.

1.3 Write Support (iOS 13+)#

Writing was added in iOS 13 for both session types:

  • NFCNDEFReaderSession: call writeNDEF(_:completionHandler:) in the readerSession(_:didDetectNDEFs:) or readerSession(_:didDetectTags:) delegate method.
  • NFCTagReaderSession: get the NFCNDEFTag protocol conformance from the detected tag, call writeNDEF(_:completionHandler:).
  • Lock (make read-only): writeLock(completionHandler:) available on both.

1.4 Background Tag Reading (iOS 14+)#

Available on iPhone XS, XS Max, XR and later (A12 Bionic+). When the screen is on (but the app is not necessarily in the foreground), iOS can scan for NDEF tags in the background. Requirements:

  • Tag must contain a Universal Link URL record as the first NDEF record.
  • App must be registered to handle that Universal Link domain.
  • The entitlement com.apple.developer.nfc.readersession.formats must include TAG.

On detection, iOS launches or foregrounds the app and delivers the tag via application(_:continue:restorationHandler:). The background reader polls approximately every 1–2 seconds. This is implemented through NFCNDEFReaderSession in background mode, not a separate API.

1.5 HCE: iOS Card Emulation — The Historical Limitation and Its Current State#

Historical state (up to iOS 17.3): iOS had zero support for third-party card emulation. CoreNFC was reader-only. The Secure Element (SE) was locked exclusively to Apple Pay / Wallet / PassKit. This was a deliberate product decision, later challenged under EU competition law.

iOS 17.4 (March 2024): Apple introduced HCE-based contactless NFC for third-party apps, initially EEA only, in response to the EU Digital Markets Act. Entitlement: com.apple.developer.nfc.hce.

iOS 18.1 (October 2024): Extended the NFC & SE Platform globally to Australia, Brazil, Canada, Japan, New Zealand, UK, USA. Uses the Secure Element (SE) path, not pure HCE, in some markets.

Practical situation for a generic survey (as of 2026-02):

The restriction is lifted but heavily gated. To use the com.apple.developer.nfc.hce entitlement, a developer must:

  1. Apply to Apple and be approved.
  2. Sign a commercial agreement with Apple.
  3. Potentially pay fees depending on use case.
  4. Commit to compliance with payment industry standards (if payment use case).

This is not a freely available developer API like NFCNDEFReaderSession. For general-purpose custom data exchange protocols, CoreNFC remains reader-only in practice. The entitlement is intended for payment, transit, identity, and similar regulated use cases. The accurate statement for a software survey is: iOS CoreNFC as a general developer API is reader-only. Card emulation exists since iOS 17.4/18.1 but only for approved developers in regulated use cases via a separate entitlement requiring Apple approval.

1.6 Entitlement Requirements#

Two entitlements govern CoreNFC behavior, declared in the .entitlements file:

com.apple.developer.nfc.readersession.formats (array of strings)

Required for all NFC reading. Valid values:

  • NDEF — for NFCNDEFReaderSession
  • TAG — for NFCTagReaderSession
  • PACE — for NFC PACE (ICAO 9303 travel document access, iOS 16+)

Both NDEF and TAG can be listed together.

com.apple.developer.nfc.readersession.iso7816.select-identifiers (Info.plist, array of strings)

Required when using ISO 7816 tags. Must list all AID (Application Identifier) hex strings the app will select. Example: "A0000002471001" (MasterCard). This prevents apps from blindly probing arbitrary AIDs.

com.apple.developer.nfc.hce (Boolean)

Gated entitlement for card emulation. Requires Apple approval.

1.7 NFCNDEFMessage and NFCNDEFPayload Structure#

NFCNDEFMessage
  └── records: [NFCNDEFPayload]
        ├── typeNameFormat: NFCTypeNameFormat (TNF)
        ├── type: Data  (Record Type Definition bytes)
        ├── identifier: Data (optional record ID)
        └── payload: Data (raw payload bytes)

TNF values (matches NFC Forum NDEF spec):

TNFValueMeaning
empty0x00No type, ID, or payload
nfcWellKnown0x01Type in NFC RTD namespace (e.g., “T”, “U”, “Sp”)
media0x02MIME type (e.g., “text/plain”)
absoluteURI0x03Absolute URI as type field
nfcExternal0x04Custom domain:type
unknown0x05Type unknown
unchanged0x06Intermediate/terminal in chunked record

Convenience constructors available: NFCNDEFPayload.wellKnownTypeURIPayload(string:) and NFCNDEFPayload.wellKnownTypeTextPayload(string:locale:).

1.8 iOS Version Requirements Summary#

FeatureMinimum iOSDevices
NFCNDEFReaderSession (read only)iOS 11iPhone 7+
NFCTagReaderSession (ISO 7816, FeliCa, ISO 15693, MIFARE)iOS 13iPhone 7+
NDEF writeiOS 13iPhone 7+
Background tag readingiOS 14iPhone XS+ (A12+)
HCE / card emulationiOS 17.4+ (EEA); iOS 18.1+ (global)iPhone 7+ (requires Apple approval)

1.9 Simulator Support#

None. CoreNFC requires physical NFC hardware. All NFC code must be tested on a physical iPhone. Attempting to initialize a session in the Simulator will receive an error immediately.


2. Android NFC#

2.1 Core Classes#

NfcManager: System service, retrieved via context.getSystemService(Context.NFC_SERVICE). Used only to get the NfcAdapter. Check nfcManager.isEnabled() for NFC state.

NfcAdapter: Central class. Methods:

  • defaultAdapter(context) — get the singleton adapter; returns null if device has no NFC hardware
  • isEnabled() — NFC on/off in system settings
  • enableForegroundDispatch(activity, pendingIntent, filters, techLists) — older dispatch mechanism
  • enableReaderMode(activity, callback, flags, extras) — newer, preferred reader mechanism
  • disableReaderMode(activity) / disableForegroundDispatch(activity) — cleanup in onPause

2.2 NFC Dispatch Mechanisms#

Intent-based dispatch (background)

When no app is using foreground dispatch or reader mode, Android resolves which app to launch via an intent filter system. Three intents in priority order:

  1. ACTION_NDEF_DISCOVERED — highest priority; fired when an NDEF-formatted tag is detected, matched against MIME type or URI scheme
  2. ACTION_TECH_DISCOVERED — fired when the tag’s tech list matches a declared tech-list in the manifest
  3. ACTION_TAG_DISCOVERED — catch-all; fired for any tag not handled by higher-priority intents

The app declares intent filters in AndroidManifest.xml and processes the tag in onNewIntent().

Foreground Dispatch (enableForegroundDispatch)

The foreground app intercepts NFC intents before the general dispatch system. Called in onResume(), disabled in onPause(). The app receives NFC events via onNewIntent(Intent intent). Tag object is extracted from intent.getParcelableExtra(NfcAdapter.EXTRA_TAG).

Limitation: Does not suppress P2P or card emulation on the local device. The NFC controller continues to offer all its modes.

Reader Mode (enableReaderMode — API 19, Android 4.4)

The preferred modern API. The activity registers a NfcAdapter.ReaderCallback that receives Tag objects directly on a separate thread, without an intent lifecycle. Key difference from foreground dispatch: reader mode instructs the NFC controller to disable P2P and card emulation modes while active. This prevents interference when the local device is also running HCE services. Flags control which technologies are polled:

NfcAdapter.FLAG_READER_NFC_A     // ISO 14443-A (MIFARE, NFC-A)
NfcAdapter.FLAG_READER_NFC_B     // ISO 14443-B
NfcAdapter.FLAG_READER_NFC_F     // FeliCa (NFC-F)
NfcAdapter.FLAG_READER_NFC_V     // ISO 15693 (NFC-V)
NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK  // Skip NDEF detection; useful for custom protocols
NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS  // Suppress system beep

The EXTRA_READER_PRESENCE_CHECK_DELAY bundle extra controls how often Android polls for tag presence (default ~125ms).

Summary: Use reader mode for new development. Foreground dispatch is legacy. Intent-based dispatch is for apps that should handle NFC when not in the foreground.

2.3 NdefMessage and NdefRecord#

NdefMessage contains an array of NdefRecord objects. NdefRecord fields:

NdefRecord
  ├── tnf: short (Type Name Format, same values as iOS)
  ├── type: byte[]
  ├── id: byte[]
  └── payload: byte[]

Factory methods: NdefRecord.createUri(Uri), NdefRecord.createMime(mimeType, payload), NdefRecord.createExternal(domain, type, data), NdefRecord.createTextRecord(language, text).

2.4 IsoDep (ISO 7816 APDU)#

IsoDep wraps ISO 14443-4 (Type A or Type B) tags. Used for smart cards, payment cards, and HCE counterparts.

IsoDep isoDep = IsoDep.get(tag);  // returns null if tag is not IsoDep
isoDep.connect();
byte[] response = isoDep.transceive(apduCommand);
isoDep.close();

transceive() sends raw APDU bytes and returns the response (including SW1/SW2 status bytes). getMaxTransceiveLength() returns the maximum supported command length (typically 261 bytes for short APDU; up to 65544 for extended APDU when supported). setTimeout(int ms) configures the transceive timeout.

ISO 7816 APDU structure: [CLA] [INS] [P1] [P2] [Lc] [Data...] [Le]

2.5 MifareClassic and MifareUltralight#

MifareClassic

MifareClassic mc = MifareClassic.get(tag);
mc.connect();
boolean auth = mc.authenticateSectorWithKeyA(sector, MifareClassic.KEY_DEFAULT);
byte[] block = mc.readBlock(blockIndex);
mc.writeBlock(blockIndex, data);
mc.close();

Key facts:

  • Sectors contain 4 blocks; each block is 16 bytes
  • Authentication uses 48-bit keys (Key A or Key B per sector)
  • KEY_DEFAULT = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, KEY_MIFARE_APPLICATION_DIRECTORY also available
  • Crypto-1 cipher is known-broken (RFID Security, de Koning Gans et al., 2008); MIFARE Classic is not suitable for high-security applications
  • Supported on most Android devices; NOT supported on iOS CoreNFC

MifareUltralight

MifareUltralight mu = MifareUltralight.get(tag);
mu.connect();
byte[] pages = mu.readPages(pageOffset);  // reads 4 pages (16 bytes)
mu.writePage(pageOffset, data);  // writes 1 page (4 bytes)
mu.close();

Key facts:

  • Pages are 4 bytes each; readPages returns 4 consecutive pages
  • MifareUltralight C adds 3DES authentication
  • No sector-based authentication like Classic

2.6 Host Card Emulation (HCE)#

HCE allows an Android app to respond to contactless NFC readers as if it were a smart card. Introduced in Android 4.4 (API 19).

HostApduService

Subclass HostApduService and declare it in the manifest:

<service android:name=".MyHceService"
         android:exported="true"
         android:permission="android.permission.BIND_NFC_SERVICE">
    <intent-filter>
        <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
    </intent-filter>
    <meta-data android:name="android.nfc.cardemulation.host_apdu_service"
               android:resource="@xml/apduservice"/>
</service>

The apduservice.xml resource declares AIDs:

<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/servicedesc"
    android:requireDeviceUnlock="false">
    <aid-group android:description="@string/aiddesc"
               android:category="other">
        <aid-filter android:name="F0394148148100"/>
    </aid-group>
</host-apdu-service>

When an NFC reader sends SELECT AID (CLA=00, INS=A4, P1=04, P2=00) with the matching AID, Android routes subsequent APDUs to the service. The service implements:

@Override
public byte[] processCommandApdu(byte[] apdu, Bundle extras) {
    // Parse apdu, return response bytes including SW1 SW2
    return response;
}

@Override
public void onDeactivated(int reason) {
    // DEACTIVATION_LINK_LOSS or DEACTIVATION_DESELECTED
}

AID conflict resolution: If multiple services register the same AID, Android checks the user’s default “tap and pay” wallet setting first, then other services, then prompts the user. Apps in the foreground can call NfcAdapter.getCardEmulationService().setPreferredService(activity, componentName) to override routing.

Category: AID groups have category="payment" (appears in Android payment settings as wallet option) or category="other" (custom, no special routing preference).

OffHostApduService: Routes to a hardware Secure Element (SIM or embedded SE) instead of the app’s process. Not covered here as it requires OEM/carrier provisioning.

2.7 NFC Reader Mode vs Foreground Dispatch — Key Distinction#

AspectForeground DispatchReader Mode
API levelAllAPI 19+
How tag is deliveredVia onNewIntent() intentVia ReaderCallback.onTagDiscovered() callback
ThreadMain threadBackground thread
P2P modeStill activeDisabled
Card emulation modeStill active on deviceDisabled on device
Polling technology controlVia IntentFilter arraysVia FLAG_READER_* bitmask
Use caseLegacy; compatible with older codePreferred; clean reader-only mode

Reader mode is especially important when the same device runs both HCE (emulating a card) and a reader app simultaneously — foreground dispatch would cause the device to respond to its own outgoing field.

2.8 Android Beam Removal and Replacement#

Android Beam used the SNEP (Simple NDEF Exchange Protocol) over NFC peer-to-peer (ISO 18092 / LLCP) to push small payloads (URLs, contacts) between Android devices. Deprecated in Android 10 (API 29, released 2019). The NfcAdapter.setNdefPushMessage() and related APIs returned null/no-ops. Android 14 removed the NDEF push service entirely.

Replacement: Google Nearby Share (now “Quick Share” after Samsung partnership). Uses Bluetooth, Wi-Fi Direct, and WebRTC for the actual transfer. NFC is used only as a bootstrap tap: one device taps the other, NFC carries a small bootstrapping packet that sets up the Bluetooth/Wi-Fi connection, then the bulk data transfers over Bluetooth or Wi-Fi. This means NFC’s role in Nearby Share is limited to pairing initiation, not data transfer.

For custom P2P NFC: There is no Android system API for arbitrary P2P NFC. Custom device-to-device NFC must be implemented as HCE (one device) + reader mode (other device), exchanging APDUs.

2.9 Tap to Pay / StrongBox#

Android supports NFC payment via HCE (as above) or via OffHostApduService (routing to SE/SIM). Tap to Pay on Android (the software SDK layer used by payment processors like Stripe, Square, Adyen) uses:

  • NfcAdapter in reader mode to accept contactless payment cards
  • The device’s NFC controller for EMV (ISO 14443 + ISO 7816) exchange
  • StrongBox Keymaster (available on select devices since API 28): a hardware security module backed by a physically isolated processor (TEE-adjacent). StrongBox can generate and store cryptographic keys used in payment flows. It is accessed via KeyPairGenerator with KeyProperties.SECURITY_LEVEL_STRONGBOX, not directly via NFC APIs.

StrongBox is separate from NFC itself; it provides the secure key storage that backs HCE payment credentials.


3. Device-to-Device NFC#

3.1 Android-to-Android#

Both devices must explicitly take roles. Android cannot do symmetric NFC (both devices acting as peers simultaneously).

Pattern: Device A runs HCE (HostApduService), Device B runs reader mode (enableReaderMode). Device B sends APDU commands; Device A’s HostApduService.processCommandApdu() responds.

Bidirectional data exchange via custom APDU protocol:

Reader (Device B)                  Emulator (Device A / HostApduService)
   → SELECT AID (F0...)            ← 90 00 (OK)
   → custom APDU (read request)    ← data + 90 00
   → custom APDU (write payload)   ← 90 00

Both devices can hold data; the reader sends commands that retrieve data from or push data to the emulator. True symmetric exchange (both issuing commands simultaneously) is not possible — one must be reader, one must be card.

For a custom bootstrap protocol: a compact APDU-based exchange can transfer hundreds of bytes in a single tap interaction (limited by IsoDep.getMaxTransceiveLength()).

3.2 iOS-to-iOS#

Two iPhones cannot exchange data via NFC. CoreNFC is reader-only. Since both devices are readers and neither emulates a card, no NFC communication is established. iOS-to-iOS NFC is not possible.

For iOS-to-iOS proximity data exchange, use Multipeer Connectivity (Bluetooth/Wi-Fi) or AirDrop.

3.3 iOS-to-Android (iOS reading Android HCE)#

This works. An Android device running a HostApduService (configured with an AID) appears to an NFC reader as an ISO 7816 smart card. The iPhone, using NFCTagReaderSession, detects it as an NFCISO7816Tag.

Flow:

  1. iPhone opens NFCTagReaderSession.
  2. Android device has HostApduService active.
  3. iPhone comes within range; iPhone detects the Android as an ISO 7816 tag.
  4. iPhone sends SELECT AID APDU via NFCISO7816Tag.sendMiFareISO7816Command(_:completionHandler:) or NFCISO7816APDU.
  5. Android processCommandApdu() handles it and responds.
  6. Arbitrary APDU exchange follows.

Practical constraints:

  • The Android device must be in “card emulation” posture (screen may need to be on; lock screen behavior varies).
  • The AID must be registered in Android’s HCE service XML.
  • iOS must list the AID in the com.apple.developer.nfc.readersession.iso7816.select-identifiers entitlement.

NDEF shortcut: If the Android HCE service emulates a NFC Forum Type 4 Tag (exposing the CC file and NDEF TLV structure), the iPhone can use NFCNDEFReaderSession to read it without any APDU-level code. Projects like NfcHceNdefEmulator on GitHub demonstrate this pattern.

3.4 What Android Beam Did vs What Replaced It#

Android BeamNearby Share (Quick Share)
NFC roleFull data transfer over NFC (SNEP/LLCP)Bootstrap tap only; data goes over BT/WiFi
RangeContact (NFC range)Unlimited once paired
Data sizeSmall (contacts, URLs, small files)Large files supported
APINfcAdapter.setNdefPushMessage()System-level, no public dev API
StatusRemoved Android 14Active (Quick Share since 2024)

3.5 Realistic Custom Bootstrap Protocol#

A typical APDU-based bootstrap exchange for initial device pairing:

1. iOS taps Android (or Android taps Android):
   → SELECT AID [7 bytes custom AID]
   ← 90 00
   → GET DATA (retrieve challenge/nonce from Android)
   ← [16-byte nonce] 90 00
   → PUT DATA (send iOS device public key or session token)
   ← 90 00
   (Optionally)
   → GET RESPONSE (receive Android's response payload)
   ← [N bytes] 90 00

Total time: ~200-500ms depending on payload sizes.

Data limit per APDU: up to 255 bytes in standard APDU, up to 65535 in extended APDU (device dependent). A complete handshake with cryptographic keys can fit in 3–5 APDU round trips.


4. React Native: react-native-nfc-manager#

Repository: https://github.com/revtel/react-native-nfc-manager

Current stats (as of 2026-02):

  • Version: 3.17.2 (latest; published late 2025)
  • Weekly downloads: approximately 33,000–34,000/week
  • GitHub stars: approximately 1,600–1,800 (search results show ~1.6K)
  • License: MIT
  • Maintainer: revtel (Taiwan-based React Native consultancy)

4.1 Platform Support#

  • iOS: CoreNFC (NFCNDEFReaderSession, NFCTagReaderSession)
  • Android: NfcAdapter with reader mode
  • Minimum iOS: 11 (NDEF); full API requires iOS 13+
  • Minimum Android: API 21 (Lollipop) per README

4.2 API Surface#

The library uses a tech-handler pattern:

import NfcManager, { NfcTech } from 'react-native-nfc-manager';

// Initialize
await NfcManager.start();

// Read NDEF
await NfcManager.requestTechnology(NfcTech.Ndef);
const tag = await NfcManager.getTag();  // returns NdefMessage
await NfcManager.cancelTechnologyRequest();

// ISO 7816 APDU (iOS + Android)
await NfcManager.requestTechnology(NfcTech.IsoDep);
const response = await NfcManager.transceive([0x00, 0xA4, 0x04, 0x00, ...aidBytes]);
await NfcManager.cancelTechnologyRequest();

// MIFARE Ultralight (Android only)
await NfcManager.requestTechnology(NfcTech.MifareUltralight);
const pages = await NfcManager.mifareUltralightHandlerAndroid.readPages(0);

NfcTech enum values:

  • Ndef — NDEF read/write (both platforms)
  • NdefFormatable — Format unformatted tags (Android)
  • IsoDep — ISO 7816 APDU transceive (both platforms)
  • MifareClassic — MIFARE Classic (Android only)
  • MifareUltralight — MIFARE Ultralight (Android only)
  • NfcA — ISO 14443-A raw (Android)
  • NfcB — ISO 14443-B raw (Android)
  • NfcF — FeliCa (Android + iOS)
  • NfcV — ISO 15693 (Android + iOS)

4.3 HCE Situation#

react-native-nfc-manager does not expose HCE in its JavaScript API. HCE requires a background Android service (HostApduService), which cannot be instantiated from JavaScript. To add HCE to a React Native app, a developer must:

  1. Write a native Android HostApduService subclass in Java/Kotlin.
  2. Register it in AndroidManifest.xml with AID XML.
  3. Optionally communicate with the JS layer via a Native Module bridge.

A separate community library react-native-hce (github: appidea/react-native-hce) provides HCE for Android only. It is much smaller (~100 stars) and not part of the mainstream ecosystem.

4.4 Other React Native NFC Libraries#

No significant alternative exists for full-featured NFC in React Native. react-native-nfc-manager is the de facto standard.


5. Flutter NFC Libraries#

5.1 nfc_manager#

Package: https://pub.dev/packages/nfc_manager Repository: https://github.com/okadan/flutter-nfc-manager Maintainer: okadan

Current stats (as of 2026-02):

  • Version: 4.1.1 (stable); 4.0.0-dev also tracked
  • Pub points: 160/160
  • Likes: approximately 522
  • Popularity score: approximately 59% (59.1k downloads lifetime)

Tag type support:

Tag TechnologyAndroidiOS
NdefYesYes
NdefFormatableYesNo
IsoDepYesYes
MifareClassicYesNo
MifareUltralightYesNo
NfcAYesNo
NfcBYesNo
NfcFYesYes
NfcVYesYes
FeliCaNoYes

The package provides a thin, idiomatic Dart wrapper over the platform NFC APIs. Tag types are detected as typed objects; the developer calls technology-specific methods. Session management mirrors CoreNFC on iOS.

5.2 flutter_nfc_kit#

Package: https://pub.dev/packages/flutter_nfc_kit Repository: https://github.com/nfcim/flutter_nfc_kit Maintainer: nfcim (community group)

Current stats (as of 2026-02):

  • Version: 3.6.0 (stable; published November 2025)
  • Pub points: 160/160
  • Likes: approximately 264
  • Popularity: approximately 31.4k downloads lifetime
  • Also supports Web NFC (Android Chrome) — unique among the Flutter options

Key differences from nfc_manager:

nfc_managerflutter_nfc_kit
Web NFCNoYes (Chrome Android)
Layer 3/4 transceiveYes (IsoDep)Yes
NDEFYesYes
Pub likes~522~264
ArchitectureTechnology-object modelSequential poll API
Last updateSep 2025Nov 2025

5.3 Maintenance Comparison#

Both packages are actively maintained as of early 2026. nfc_manager has approximately 2x the community adoption (likes) and a cleaner API design aligned with platform SDK patterns. flutter_nfc_kit is the choice if Web NFC (Android Chrome) support is required. For new projects without web requirements, nfc_manager is the better default.


6. Web NFC#

6.1 Specification Status#

Web NFC is specified by the W3C Web NFC Community Group. It is not a W3C Recommendation and is not on the W3C Standards Track. The original NFC Working Group published their work as a Working Group Note (meaning the WG concluded without advancing to Recommendation). The current specification lives at https://w3c.github.io/web-nfc/ and is a Community Group Report.

6.2 Browser Support#

BrowserSupport
Chrome for Android (89+)Yes — shipped by default since Chrome 89 (March 2021)
Chrome desktopNo (no NFC hardware)
FirefoxNo
Safari (iOS or macOS)No — Apple has not implemented Web NFC
EdgeNo
Samsung InternetPartial (follows Chromium)

Web NFC requires a secure context (HTTPS). Operations must be initiated by a user gesture.

6.3 NDEFReader API#

const reader = new NDEFReader();

// Read
await reader.scan();
reader.onreading = ({ message, serialNumber }) => {
    for (const record of message.records) {
        // record.recordType, record.data (ArrayBuffer), record.mediaType, etc.
    }
};

// Write
await reader.write({ records: [{ recordType: "url", data: "https://example.com" }] });

// Make read-only
await reader.makeReadOnly();

NDEFReader is on the window (or navigator in some drafts). Detection: 'NDEFReader' in window.

6.4 Limitations#

  • NDEF only: No raw protocol access. No ISO 7816 APDU. No MIFARE Classic commands. No FeliCa direct access.
  • No HCE: Cannot emulate a card.
  • No P2P: Cannot communicate with another Web NFC client.
  • Android Chrome only: No iOS, no desktop.
  • User gesture required: Scan/write must be triggered by a user action (button click, etc.).
  • HTTPS required: Does not work on HTTP.
  • No background reading: The page must be in the foreground.

6.5 Origin Trial History#

Web NFC went through a Chrome origin trial from approximately 2019–2021 before shipping by default in Chrome 89. There is no current origin trial; it is fully shipped in Chrome Android.


7. Physical Characteristics#

7.1 Frequency#

NFC operates at 13.56 MHz, in the globally unlicensed ISM (Industrial, Scientific, and Medical) band. This is the same frequency as ISO 14443 contactless smart cards, ISO 15693 vicinity tags, and FeliCa.

7.2 Operating Distance#

ScenarioTypical RangeNotes
Passive tag read (phone to tag)0–4 cmTag powered by reader’s RF field
Active device-to-device0–20 cmBoth devices have powered antennas; 10 cm is practical
NFC Forum certified compliance≤5 mmStandard compliance test distance
Practical tap interaction0–2 cmUser-initiated contact tap

The NFC Forum specifies a working distance of up to 20 cm, but consumer devices are tuned for 4 cm or less for intentional tap interactions. Longer range (up to 20 cm) is achievable with powered devices and larger antennas.

7.3 Data Rate#

NFC supports multiple data rates: 106, 212, and 424 kbps (standard), with some implementations supporting 848 kbps. Proprietary extensions (NFC-V/ISO 15693) can reach different rates.

At 106 kbps:

  • 106,000 bits/second = ~13.25 KB/s effective throughput before protocol overhead
  • A 256-byte APDU response takes approximately 20–25 ms at the wire level

7.4 Read/Write Timing#

Observed timings (vary by device, tag, and payload size):

OperationTypical TimeNotes
Tag detection (polling loop)20–100 msTime from physical proximity to first callback
NDEF read (small tag, 48 bytes)100–200 ms totalIncludes session establishment
NDEF read (large record, 512 bytes)200–400 ms
NDEF write200–500 msHigher variance; flash write on tag
ISO 7816 SELECT AID50–100 msAfter tag detection
Single APDU exchange10–30 msAfter connection established
Full APDU handshake (3–5 commands)50–200 msFor custom protocol

The total time a user must hold devices together for a simple tap-to-read: approximately 300–700 ms including Android polling overhead and user interaction.

7.5 Security Characteristics#

Eavesdropping

NFC is not inherently encrypted at the physical layer. An attacker with a large antenna can passively eavesdrop on NFC exchanges:

  • Passive eavesdropping of an active device: up to approximately 1 meter with modest equipment
  • Passive eavesdropping of a passive tag: up to approximately 10 meters with research-grade equipment (large directional antenna, no amplification)

Defense: Implement application-layer encryption (TLS session over NFC, or NFC-SEC ISO 13157). For payment use cases, EMV cryptograms handle this.

Relay Attacks

An attacker places two devices: one near the legitimate reader, one near the legitimate card. The attacker relays APDU commands between them in real time, allowing fraudulent transactions at a distance. Demonstrated against contactless payment cards and transit cards. Mitigation: timing constraints (check response latency), distance bounding protocols.

Jamming

An RF jammer at 13.56 MHz can prevent NFC communication (denial of service). No software mitigation; physical proximity to a legitimate reader is the natural defense.

MIFARE Classic Cryptographic Weakness

The Crypto-1 cipher used in MIFARE Classic is proprietary and has been fully reverse-engineered. Known attacks (Nested attack, Darkside attack) can recover all sector keys from a few hundred authentication attempts or partial information. MIFARE Classic should not be used for new high-security deployments. MIFARE DESFire EV2/EV3 (AES-128) or MIFARE Plus SL3 are the recommended alternatives.


Summary of Key Corrections and Nuances#

  1. iOS HCE: Not “impossible” — possible since iOS 17.4/18.1 but gated behind Apple approval, commercial agreements, and regulated use cases. For a general developer, CoreNFC is still read-only.

  2. MIFARE Classic on iOS: Deliberately unsupported by Apple (Crypto-1 not implemented), not a hardware limitation.

  3. Android Reader Mode: Does more than foreground dispatch — it actively disables P2P and card emulation on the local NFC controller, not just intercepts intents.

  4. Android Beam: Deprecated API 29, fully removed in Android 14 (not Android 10 as sometimes stated). Nearby Share uses NFC only for Bluetooth/WiFi bootstrap, not for data transfer.

  5. Web NFC spec: Community Group report, not W3C Recommendation; not on standards track.

  6. react-native-nfc-manager HCE: The library does not expose HCE in JS; requires native Android service code.

  7. iOS reads Android HCE: Fully supported and practical — iOS NFCTagReaderSession treats Android HCE as an IsoDep tag.

  8. flutter_nfc_kit: Latest stable is 3.6.0 (not 3.4); it supports Web NFC which nfc_manager does not.


S2 Recommendation: NFC / Near-Field Communication Libraries#

Decision Table#

NeediOSAndroidReact NativeFlutter
Read NDEF tagsNFCNDEFReaderSessionNfcAdapter + Ndefreact-native-nfc-managernfc_manager
Write NDEF tagsNFCNDEFReaderSession (iOS 13+)Ndef.writeNdefMessagereact-native-nfc-managernfc_manager
ISO 7816 APDUNFCTagReaderSessionNFCISO7816TagIsoDep.transceiveNfcTech.Iso7816 / NfcTech.IsoDepnfc_manager
Card emulation (HCE)❌ Not possibleHostApduServiceNative Android onlyNative Android only
MIFARE ClassicNFCTagReaderSessionNFCMiFareTagMifareClassicNfcTech.MifareClassic (Android)nfc_manager
Background tag readingiOS 14+ (URL records only)Intent-basedN/A (platform feature)N/A

Definitive Rankings#

React Native: react-native-nfc-manager — only serious maintained option, comprehensive API Flutter: nfc_manager — preferred over flutter_nfc_kit, better maintained iOS native: CoreNFC — NFCTagReaderSession for full control, NFCNDEFReaderSession for simple NDEF Android native: NfcAdapter with reader mode for reading; HostApduService for card emulation


S2 Synthesis: NFC / Near-Field Communication Libraries#

What S2 Confirmed and Added#

1. NDEF External Types Enable Custom Protocols Without APDU#

TNF 0x04 (External Type) allows apps to embed arbitrary bytes in NDEF records with a custom type identifier (domain:type). This is lighter-weight than ISO 7816 APDU for simple tag-based data exchange — no AID registration, no smart card emulation. Appropriate when the tag is a passive carrier (not another phone’s HCE).

2. The APDU SELECT AID Flow Is the Entry Point for Custom Protocols#

For device-to-device exchanges via HCE, the SELECT AID command is mandatory first. The AID identifies the application. AID choice matters: payment AIDs (starting with A000…) require Google certification; other category AIDs (e.g., starting with F0) have no restrictions. Use a 7-byte AID in the other category for custom applications.

3. MIFARE Classic Should Not Be Used for New Secure Applications#

CRYPTO1 is thoroughly broken. MIFARE Ultralight C (3DES), MIFARE DESFire EV2/EV3 (AES), or application-layer AES over any tag type are the correct choices for security-sensitive applications.

4. HCE Cannot Be Fully Driven From JavaScript in React Native#

HostApduService.processCommandApdu() must return a response synchronously. JavaScript bridge calls are asynchronous. Developers who need JavaScript involvement in HCE responses must use a native service that shares state with the JS layer (e.g., via a bound service + event bridge) — not a pure JS solution.

5. iOS Background Tag Reading Is Narrowly Scoped#

Background tag reading (iOS 14+) works only for URL records matching the app’s Associated Domains. It does not allow background APDU execution or arbitrary NDEF payload processing. Its value is limited to “tap-to-open-app” use cases.

6. Physical Hold Time Is a UX Constraint#

Multi-APDU exchanges (4+ round trips) take 400–800 ms. Users must maintain the NFC field during the entire exchange. Protocol design should minimize round trips. Batch data in single APDU payloads where possible.

S3: Need-Driven

S3 Approach: NFC / Near-Field Communication Libraries#

Research ID: 1.119.1 Pass: S3 — Need-Driven Discovery Date: 2026-02-17

Personas and Use Cases#

  1. App developer: Read URL/vCard from NFC tags, launch app or action
  2. IoT/hardware developer: Read/write MIFARE tags on access control systems
  3. Payment integrator: Understand HCE for custom payment/loyalty flows
  4. Protocol designer: Device-to-device key or data exchange via APDU
  5. Web developer: Lightweight NFC tag interaction without native app

S3 Library Comparison: NFC / Near-Field Communication Libraries#

Feature Matrix#

Featurereact-native-nfc-managernfc_manager (Flutter)flutter_nfc_kitCoreNFC (iOS)Android NfcAdapter
PlatformReact Native (iOS + Android)Flutter (iOS + Android)Flutter + WebiOS nativeAndroid native
NDEF read
NDEF write✅ (iOS 13+)
ISO 7816 APDU
MIFARE ClassicAndroid onlyAndroid onlyAndroid only
MIFARE Ultralight
MIFARE DESFire✅ (via APDU)✅ (via APDU)✅ (via APDU)✅ (via APDU)
HCE (card emulation)❌ (JS layer)❌ (JS layer)❌ (general)
FeliCaPartial
ISO 15693
Web NFCN/A
Background readingPlatform featurePlatform featureN/AiOS 14+ (URLs)Intent-based
Stars / Popularity~1,600 / ~33K/week160 pts / ~522 likes160 pts / ~264 likesN/A (Apple)N/A (Google)
Version3.17.24.1.13.6.0iOS SDKAndroid SDK
LicenseMITMITMITApple SDKAndroid SDK
Simulator support✅ (limited)

HCE Situation Across Libraries#

None of the cross-platform libraries expose HCE in their JavaScript/Dart API:

  • react-native-nfc-manager: No JS HCE. HostApduService.processCommandApdu() is synchronous — cannot be driven from an async JS bridge. Need native Android module.
  • nfc_manager and flutter_nfc_kit: Same constraint. HCE requires native Android service code; Flutter’s platform channel cannot satisfy the synchronous response requirement.
  • CoreNFC (iOS): General HCE not available. iOS 17.4/18.1 added HCE globally but requires Apple commercial approval — not accessible for custom protocols.
  • Android NfcAdapter: Full HCE via HostApduService. The only platform with true general-purpose card emulation.

Web NFC Comparison#

NDEFReader (Web NFC)flutter_nfc_kitreact-native-nfc-manager
NDEF read✅ (web)❌ (no web)
NDEF write✅ (web)❌ (no web)
APDU accessN/A
iOS supportN/A
Desktop supportN/A
Android Chrome 89+✅ (via NDEFReader)N/A

Decision Matrix#

ScenarioRecommended LibraryWhy
React Native, NDEF onlyreact-native-nfc-managerDefault choice
React Native, APDU/custom protocolreact-native-nfc-managerFull APDU support
React Native, HCE neededreact-native-nfc-manager + native Android moduleNo JS-only solution
Flutter, standard mobilenfc_managerBetter maintained, cleaner API
Flutter, Web NFC requiredflutter_nfc_kitOnly Flutter library with Web NFC
iOS nativeCoreNFC directlyNo abstraction adds value
Android nativeNfcAdapter + HostApduServicePlatform SDK is complete
Browser (Android Chrome)NDEFReader (Web NFC)No alternative
MIFARE Classic legacyAndroid native onlyiOS does not support Crypto-1

S3 Recommendation: NFC / Near-Field Communication Libraries#

Top Library Per Persona#

PersonaPlatformLibraryReasoning
App developer (URL tags)React Nativereact-native-nfc-managerOnly serious option; NDEF read/write built-in
App developer (URL tags)Flutternfc_managerClean API, 160/160 pub points, active
App developer (URL tags)WebWeb NDEFReaderNo install required; Android Chrome only
IoT/hardware developerAndroid nativeNfcAdapter + IsoDepDirect APDU access, no library overhead
IoT/hardware developeriOS nativeNFCTagReaderSessionFull ISO 7816 APDU control
Payment integratorAndroidHostApduService (native)HCE only available natively
Protocol designerAndroid nativeIsoDep.transceiveDirect byte-level control
Web developerAny (Android Chrome)Web NDEFReaderZero install, works in browser

Critical Trade-offs#

For HCE (phone as card): Always requires native Android code — no cross-platform abstraction handles this cleanly. Both react-native-nfc-manager and nfc_manager leave HCE to native modules.

For iOS APDU: NFCTagReaderSession is the only path. Cross-platform libraries wrap it, but with thin wrappers — minimal abstraction cost.

For Web: Accept NDEF-only limitation. Web NFC is appropriate for simple tag reads and writes in Android Chrome; it cannot do any advanced protocol work.

Decision Rule#

  • Cross-platform app → use the framework’s de facto library (react-native-nfc-manager or nfc_manager)
  • Native iOS → CoreNFC directly; no third-party adds value
  • Native Android → NfcAdapter directly for reads; HostApduService for card emulation
  • Web → NDEFReader (Android Chrome only; plan for graceful degradation)

S3 Use Cases: NFC / Near-Field Communication Libraries#


Use Case 1: Read a URL Tag and Open In-App (React Native)#

Scenario: Marketing tags, product labels, or event badges encode a URL. Tapping opens the app at a specific screen.

import NfcManager, { NfcTech, Ndef } from 'react-native-nfc-manager';

export async function startNfcUrlListener(onUrl: (url: string) => void) {
  await NfcManager.start();
  await NfcManager.requestTechnology(NfcTech.Ndef);

  const tag = await NfcManager.getTag();
  const record = tag?.ndefMessage?.[0];

  if (record && record.tnf === Ndef.TNF_WELL_KNOWN) {
    const decoded = Ndef.uri.decodePayload(new Uint8Array(record.payload));
    onUrl(decoded);
  }

  await NfcManager.cancelTechnologyRequest();
}

iOS alternative (native): Background tag reading (iOS 14+) handles this without user interaction — the URL opens the app automatically via universal links. No code needed beyond the Associated Domains entitlement.


Use Case 2: Write a Custom NDEF Tag#

Scenario: A configuration tool writes setup data to blank NFC tags that are then attached to hardware devices.

// react-native-nfc-manager
import NfcManager, { NfcTech, Ndef } from 'react-native-nfc-manager';

async function writeConfigTag(deviceId: string, config: object) {
  await NfcManager.requestTechnology(NfcTech.Ndef);

  // Write custom external type record
  const jsonBytes = Ndef.encodeMessage([
    Ndef.record(
      Ndef.TNF_EXTERNAL_TYPE,
      'com.mycompany:deviceconfig',
      '',
      JSON.stringify({ deviceId, config })
    )
  ]);

  await NfcManager.ndefHandler.writeNdefMessage(jsonBytes);
  await NfcManager.cancelTechnologyRequest();
}
// Flutter: nfc_manager
import 'package:nfc_manager/nfc_manager.dart';

Future<void> writeTag(String data) async {
  NfcManager.instance.startSession(onDiscovered: (NfcTag tag) async {
    final ndef = Ndef.from(tag);
    if (ndef == null || !ndef.isWritable) return;

    final message = NdefMessage([
      NdefRecord.createExternal('com.mycompany', 'config', utf8.encode(data)),
    ]);
    await ndef.write(message);
    NfcManager.instance.stopSession();
  });
}

Use Case 3: ISO 7816 APDU — Custom Data Exchange Protocol#

Scenario: A device embeds an NFC chip. The mobile app reads structured data via APDU commands (multi-step protocol: SELECT AID → GET CHALLENGE → READ DATA).

// react-native-nfc-manager — iOS
import NfcManager, { NfcTech } from 'react-native-nfc-manager';

async function readSecureTag() {
  // iOS uses Iso7816, Android uses IsoDep
  const tech = Platform.OS === 'ios' ? NfcTech.Iso7816 : NfcTech.IsoDep;
  await NfcManager.requestTechnology(tech, {
    iso18092VicinityChecking: false,
  });

  // 1. SELECT AID
  const selectCmd = [0x00, 0xA4, 0x04, 0x00, 0x07,
    0xF0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00];
  const selectResp = await NfcManager.isoDepHandler.transceive(selectCmd);
  const [sw1, sw2] = selectResp.slice(-2);
  if (sw1 !== 0x90 || sw2 !== 0x00) throw new Error('SELECT failed');

  // 2. GET DATA (custom command 0xCA)
  const getDataCmd = [0x00, 0xCA, 0x00, 0x01, 0x00];
  const data = await NfcManager.isoDepHandler.transceive(getDataCmd);
  const payload = data.slice(0, -2);  // strip SW1 SW2
  const decoded = JSON.parse(new TextDecoder().decode(new Uint8Array(payload)));

  await NfcManager.cancelTechnologyRequest();
  return decoded;
}

Kotlin (Android native):

suspend fun readSecureTag(tag: Tag): ByteArray? {
    val isoDep = IsoDep.get(tag) ?: return null
    isoDep.use { dep ->
        dep.connect()
        dep.timeout = 3000

        val selectResp = dep.transceive(byteArrayOf(
            0x00, 0xA4.toByte(), 0x04, 0x00, 0x07,
            0xF0.toByte(), 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00
        ))
        if (selectResp[selectResp.size-2] != 0x90.toByte()) return null

        val dataResp = dep.transceive(byteArrayOf(0x00, 0xCA.toByte(), 0x00, 0x01, 0x00))
        return dataResp.dropLast(2).toByteArray()
    }
}

Use Case 4: Android HCE — Phone as Smart Card#

Scenario: An Android app acts as a contactless card that other NFC readers can interact with. Custom protocol: reader sends SELECT AID, phone responds with session data.

// Full HCE service
class MyCardService : HostApduService() {
    private var sessionActive = false

    companion object {
        val SELECT_CMD_HEADER = byteArrayOf(0x00, 0xA4.toByte(), 0x04, 0x00)
        val MY_AID = byteArrayOf(0xF0.toByte(), 0x01, 0x02, 0x03, 0x04, 0x05, 0x06)
        val OK = byteArrayOf(0x90.toByte(), 0x00)
        val NOT_FOUND = byteArrayOf(0x6A, 0x82.toByte())
    }

    override fun processCommandApdu(apdu: ByteArray, extras: Bundle?): ByteArray {
        if (apdu.size < 4) return NOT_FOUND

        return when {
            // SELECT AID
            apdu.take(4).toByteArray().contentEquals(SELECT_CMD_HEADER) -> {
                val aidLen = apdu[4].toInt() and 0xFF
                val aid = apdu.slice(5..5+aidLen-1).toByteArray()
                if (aid.contentEquals(MY_AID)) {
                    sessionActive = true
                    OK
                } else NOT_FOUND
            }
            // Custom GET DATA command
            sessionActive && apdu[1] == 0xCA.toByte() -> {
                val responseData = buildSessionPayload()
                responseData + OK
            }
            else -> NOT_FOUND
        }
    }

    private fun buildSessionPayload(): ByteArray {
        val json = """{"session":"${UUID.randomUUID()}","ts":${System.currentTimeMillis()}}"""
        return json.toByteArray(Charsets.UTF_8)
    }

    override fun onDeactivated(reason: Int) {
        sessionActive = false
    }
}

Use Case 5: MIFARE Ultralight — Simple Access Tag#

Scenario: Small NFC tags (keyfobs, stickers) store a short identifier. MIFARE Ultralight is common, inexpensive, and has no authentication sector (unlike Classic).

// react-native-nfc-manager
import NfcManager, { NfcTech } from 'react-native-nfc-manager';

async function readUltralightTag() {
  await NfcManager.requestTechnology(NfcTech.MifareUltralight);

  // Pages 4+ are user data (pages 0-3 are UID/lock/OTP)
  const page4 = await NfcManager.mifareUltralightHandlerAndroid.readPages(4);
  // Returns 4 pages × 4 bytes = 16 bytes

  const text = new TextDecoder().decode(new Uint8Array(page4));
  await NfcManager.cancelTechnologyRequest();
  return text.trim();
}

Use Case 6: Web NFC — Tag Reading in Browser#

Scenario: A web app on Android Chrome reads NFC tags without requiring a native app install.

// Chrome Android only (no iOS, no desktop)
async function readNfcTag() {
  if (!('NDEFReader' in window)) {
    alert('Web NFC not supported. Use Chrome on Android.');
    return;
  }

  const reader = new NDEFReader();
  await reader.scan();  // triggers permissions prompt

  reader.addEventListener('reading', ({ message, serialNumber }) => {
    console.log(`Tag UID: ${serialNumber}`);
    for (const record of message.records) {
      if (record.recordType === 'text') {
        const lang = record.lang;
        const text = new TextDecoder(record.encoding).decode(record.data);
        console.log(`Text [${lang}]: ${text}`);
      } else if (record.recordType === 'url') {
        const url = new TextDecoder().decode(record.data);
        console.log(`URL: ${url}`);
      } else if (record.recordType === 'mime') {
        // record.mediaType = 'application/json'
        const json = JSON.parse(new TextDecoder().decode(record.data));
      }
    }
  });
}

Limitations: No APDU access, no HCE, no MIFARE. NDEF records only. Cannot work on iOS.


Library Comparison Table#

Factorreact-native-nfc-managernfc_manager (Flutter)CoreNFC (iOS)Android NfcAdapter
NDEF read/write
ISO 7816 APDU
MIFARE ClassicAndroid onlyAndroid only
MIFARE Ultralight
HCE (card emulation)Native Android onlyNative Android only
FeliCa
ISO 15693
Background readingPlatform featurePlatform featureiOS 14+ (URLs)Intent-based
Stars/downloads~1,600 / ~33K/wk~160 pts / ~522 likesN/A (Apple)N/A (Google)
Simulator support✅ (emulator basic)

Decision Tree#

Need NFC in your app?
├── React Native?
│   └── react-native-nfc-manager (only serious option)
├── Flutter?
│   └── nfc_manager (preferred over flutter_nfc_kit)
├── Native iOS?
│   ├── Simple NDEF → NFCNDEFReaderSession
│   └── APDU / MIFARE / FeliCa → NFCTagReaderSession
├── Native Android?
│   ├── Read tags → NfcAdapter + reader mode
│   ├── Custom APDU → IsoDep.transceive
│   └── Card emulation → HostApduService (HCE)
└── Web only (Android Chrome)?
    └── Web NFC NDEFReader (NDEF only)
S4: Strategic

S4 Strategic Analysis: NFC / Near-Field Communication Libraries#

Research ID: 1.119.1 Pass: S4 — Strategic Analysis Date: 2026-02-17


1. Platform Policy Risk#

Apple’s Controlled Opening#

Apple’s NFC policy has shifted from reader-only (iOS 11, 2017) toward card emulation, but under strict controls:

  • iOS 17.4 (March 2024): Added HostApduService-equivalent functionality in the EEA, under the Digital Markets Act. Requires Apple application, commercial agreement, and regulated use case (payment, transit, identity, hotel keys, car keys, tickets).
  • iOS 18.1 (October 2024): Extended globally.
  • Developer entitlement: com.apple.developer.nfc.hce — not available via standard Apple Developer Program enrollment. Requires a separate approval process.

Strategic implication: Apple has cracked the door for card emulation but controls the key. For payment/transit/identity integrators: this opens real iOS HCE capability. For general custom NFC apps: nothing has changed. Apple’s pattern suggests continued incremental opening aligned with regulatory and commercial pressure, not open access.

Google’s Stable Path#

Android’s NFC stack (NfcAdapter + HostApduService) has been stable since API 19 (Android 4.4, 2013). There are no announced deprecations. Google continues HCE development (NFC multi-SIM support, StrongBox integration for payment credentials). Reader mode (API 19) remains the preferred reading mechanism. No disruptive changes expected on Android.


2. Web NFC Trajectory#

Web NFC has been shipping in Chrome for Android since Chrome 89 (March 2021) with no significant expansion:

  • Still Android Chrome only — no Firefox, no Safari, no desktop Chrome
  • Not on W3C Standards Track — remains a Community Group report, not a W3C Recommendation
  • Apple’s position: No public signal of intent to implement in Safari or WebKit. Safari has zero NFC hardware on iOS anyway (iOS NFC access requires entitlements, not available to WebKit).
  • Firefox: No announced NFC roadmap

Assessment: Web NFC is stable and shipping but geographically constrained (Android Chrome) and organizationally constrained (community group, not standard). It will not become a universal web API in the near term. Its value proposition — frictionless NFC interaction without app install — remains real but narrow in scope.


3. Alternative Proximity Technologies#

When evaluating NFC, consider the alternatives:

TechnologyRangeSpeedKey Advantage vs NFC
BLE10–100 mMediumBackground operation, longer range, no physical tap required
UWB (Ultra-Wideband)0–10 mHighCentimeter-level ranging/positioning; supported on iPhone 11+, Pixel 6+
Wi-Fi Aware (NAN)0–30 mHighPeer-to-peer without an AP; no pairing required
QR Code0.5–2 m (camera)LowNo hardware required (works on any camera)

UWB is the most significant trend: iPhone’s U1/AX chip enables centimeter-level distance measurement (Apple AirTags, Precision Finding, CarKey). For use cases requiring proximity verification with precision, UWB provides superior security properties. Android devices are adopting UWB (Google Pixel 6+, Samsung Galaxy S21+). The tap interaction paradigm may shift toward UWB-based proximity detection with no physical contact required.

NFC’s advantage: intentionality. The physical tap is a user gesture that clearly signals intent. This “tap = consent” UX model has real value for access control, payment authorization, and data exchange scenarios where accidental triggering must be avoided.


4. Cross-Platform Library Longevity#

react-native-nfc-manager#

  • Maintainer: revtel (Taiwan-based RN consultancy) — professional shop with commercial stake in RN
  • Activity: Regular releases (3.17.x in 2026), active GitHub issues
  • Risk: Single maintainer organization; if revtel pivots away from React Native, the library could stagnate. However, it has no serious competition — a community fork would likely emerge
  • Verdict: Stable for 3–5 year horizon. Only realistic option for React Native NFC

nfc_manager (Flutter)#

  • Maintainer: okadan (individual)
  • Activity: Version 4.1.1 (2025), 160/160 pub points, ~522 likes
  • Risk: Individual maintainer; sustainability depends on personal interest
  • Alternative: flutter_nfc_kit (nfcim group) as fallback — similar capability set
  • Verdict: Acceptable for production. The Flutter ecosystem has demonstrated community ability to fork and maintain critical packages when original maintainers step back

Native APIs (CoreNFC, Android NfcAdapter)#

  • Risk: Near zero — maintained by Apple and Google respectively
  • Stability: CoreNFC changes are additive (never removed existing session types); Android NFC has been stable for 12+ years
  • Verdict: The highest-reliability path for any NFC application

5. MIFARE Classic End-of-Life Pressure#

MIFARE Classic (Crypto-1) has been cryptographically broken since 2008. Multiple published attacks (Nested, Darkside, Card-Only) recover sector keys in minutes to seconds. Despite this:

  • Millions of legacy transit, access control, and identification systems still use MIFARE Classic
  • Replacement is ongoing (DESFire EV2/EV3, MIFARE Plus SL3) but slow due to infrastructure costs
  • Apple deliberately omits Crypto-1 from CoreNFC

Strategic recommendation: Do not use MIFARE Classic for any new deployment. Migrating to MIFARE DESFire EV2+ or ISO 7816 with application-layer AES is the correct path. For legacy interop, Android native (MifareClassic API) is the only supported path.


Anti-relay: For payment and access control, relay attacks are an active threat. Timing-based countermeasures (checking response latency) and distance bounding protocols are industry responses. UWB can provide cryptographic distance bounds; NFC alone cannot.

Application-layer encryption: Critical for any sensitive exchange. NFC-SEC (ISO 13157) specifies ECDH key agreement over NFC, but is rarely implemented in commercial products. More commonly, developers use TLS session establishment over NFC, or pre-established shared keys, or Noise Protocol framework sessions.

Passkeys/FIDO2 via NFC: Hardware security keys (YubiKey, Feitian) use NFC to deliver FIDO2 responses. This is a stable and growing use case — authenticator apps and platform wallets are adding FIDO2 via NFC. The WebAuthn stack handles the protocol; NFC is just the transport.


S4 Approach: NFC / Near-Field Communication Libraries#

Research ID: 1.119.1 Pass: S4 — Strategic Selection Date: 2026-02-17

Evaluation Dimensions#

  1. Platform convergence risk: Dependency on Apple/Google NFC policy decisions
  2. Web NFC trajectory: Will it escape Chrome Android? Standards body status?
  3. HCE stability: Android’s card emulation path, EMV ecosystem changes
  4. Library longevity: Maintenance outlook for cross-platform wrappers
  5. Security posture: MIFARE Classic end-of-life pressure, post-quantum irrelevance
  6. Alternative proximity technologies: BLE, UWB, Wi-Fi Aware as NFC substitutes

S4 Recommendation: NFC / Near-Field Communication Libraries#

Definitive Stack Recommendation#

React Native#

react-native-nfc-manager — no alternatives. Version 3.17.x, ~33K downloads/week. Use with native Android HostApduService for HCE; the library does not expose HCE in JavaScript.

Flutter#

nfc_manager (v4.1.1, 160 pub points) — preferred for most projects. flutter_nfc_kit (v3.6.0) — if Web NFC (Android Chrome) support is required; its unique differentiator.

iOS Native#

CoreNFC (NFCTagReaderSession for APDU/MIFARE/FeliCa; NFCNDEFReaderSession for simple NDEF). No third-party libraries add meaningful value over the platform SDK.

Android Native#

NfcAdapter with reader mode for reading. HostApduService for card emulation (HCE). No third-party libraries needed for native development.

Web#

NDEFReader (Web NFC API) — Android Chrome 89+ only. Use as progressive enhancement; always provide a fallback for non-Chrome-Android users.


The One Principle#

NFC is asymmetric. In every device-to-device interaction, one party is the reader and one is the card. iOS can only be the reader. Android can be either. Design your protocol knowing which role each device plays before writing a line of code.


Tag Selection for New Deployments#

Use CaseTag TypeReason
URL/NDEF data carrierNTAG213/215/216Cheap, universal, NDEF-formatted from factory
Access control (secure)MIFARE DESFire EV3AES-128, mutual authentication, anti-clone
Configuration tagsNTAG213Writable, lockable, $0.10–$0.50
Payment/transitDefined by scheme (EMV, etc.)Not a free choice — scheme dictates tag type

Do not use MIFARE Classic for any new deployment. The Crypto-1 cipher is broken.


Architecture Decision: NDEF vs APDU#

Use NDEF External Type when:

  • Tag is a passive carrier (sticker, keyfob)
  • Protocol is simple (one-shot read of device config, URL, or short data)
  • No multi-step challenge/response required

Use ISO 7816 APDU when:

  • Protocol requires multiple round trips (challenge/response, key exchange)
  • One device is emulating a smart card (HCE)
  • You need authentication, chaining, or conditional responses

NDEF external type (TNF_EXTERNAL_TYPE, domain:localname) gives you arbitrary bytes in a tag with zero AID registration overhead. APDU gives you a real protocol engine but requires SELECT AID, requires Android HCE or a smart card, and is more complex to implement.


Sources#


S4 Viability: NFC / Near-Field Communication Libraries#

Research ID: 1.119.1 Pass: S4 — Viability Assessment Date: 2026-02-17


Long-Term Viability Scores#

Technology5-Year ViabilityRationale
CoreNFC (iOS)✅ HighApple’s NFC stack is stable; incremental expansion toward HCE
Android NFC (NfcAdapter/HCE)✅ High12+ year stable API; no deprecation signals
react-native-nfc-manager🟡 Medium-HighSingle maintainer org; no competition, but succession risk
nfc_manager (Flutter)🟡 MediumIndividual maintainer; flutter_nfc_kit as credible fallback
Web NFC🟡 MediumShipping in Chrome Android, not expanding; not on standards track
MIFARE Classic❌ LowCryptographically broken; sunset pressure from infrastructure owners
MIFARE DESFire EV2/EV3✅ HighIndustry standard for secure contactless; AES-based
NFC Forum NDEF✅ HighCore interoperability standard; not going away

When NFC Is the Right Choice#

Yes, use NFC when:

  • Physical tap is meaningful UX (consent-to-share, access control, product authentication)
  • No infrastructure required at the reader (passive tags are extremely cheap)
  • Short-range requirement is a security feature (proximity = intent)
  • Interoperability with standard readers is required (ISO 7816 for transit/payment)
  • Passive data carrier (configuration tags on hardware devices)

Consider alternatives when:

  • Background proximity detection needed (BLE beacons, UWB)
  • Longer range required (Wi-Fi Aware, BLE)
  • Centimeter-level distance measurement required (UWB)
  • iOS-to-iOS communication needed (Multipeer Connectivity over BLE/Wi-Fi)
  • Symmetric P2P required between equal-role devices (HCE + reader split is asymmetric)

Risk Matrix#

RiskLikelihoodImpactMitigation
Apple restricts NFC furtherLowHighBuild native fallback to BLE for critical paths
react-native-nfc-manager abandonedLowMediumMigrate to native modules (thin wrapper)
MIFARE Classic still in productionHigh (legacy)MediumAndroid-only handling; note iOS limitation
Web NFC never gains Safari supportHighLowWeb NFC as enhancement, not dependency
UWB displaces NFC tap use casesMedium (5+ years)MediumNFC tap UX is durable; UWB complements rather than replaces

Investment Recommendation#

Low-risk bet: Native CoreNFC + NfcAdapter/HostApduService. Platform APIs from Apple and Google are the most durable choice.

Cross-platform bet: react-native-nfc-manager (RN) or nfc_manager (Flutter) cover 90% of use cases with acceptable maintenance risk.

Avoid: New MIFARE Classic deployments. Web NFC as a primary user experience for anything requiring broad device coverage.

Watch: iOS HCE expansion (Apple’s commercial approval program may broaden), UWB-NFC hybrid proximity protocols, FIDO2 over NFC for passkey delivery.

Published: 2026-03-06 Updated: 2026-03-06