Skip to content

Card Interaction

Implementation, constraints, and selection logic for interacting with the United Network card using a web browser or the mobile application.

Interaction Methods

Communication between the client device and the United Network card is achieved via two primary protocols: FIDO (WebAuthn) and WebNFC. Neither method is universally supported across all platforms, necessitating a hybrid approach.

1. FIDO (WebAuthn over NFC)

The FIDO protocol utilizes the browser's Credential Management API to send commands to the card. The card acts as a security key.

  • iOS + Safari: Fully supported. This is the primary method for Apple mobile devices.
  • Android + Chrome: Partially supported. The Android FIDO implementation does not support commands with a length greater than 256 bytes. This limitation restricts functionality to basic operations like single secp256k1 signatures (non-batch).
  • United Network Application: The native application uses the FIDO protocol for card communication on both Android and iOS platforms.

2. WebNFC

WebNFC is a browser API that provides direct access to NFC tags for reading and writing NDEF records.

  • iOS + Safari: Not supported by Apple.
  • Android + Chrome: Fully supported (Browser version 89 and above). This method allows for larger data transfers, overcoming the 256-byte limit of FIDO on Android.

Selection Logic

The application automatically determines the best interaction method based on the environment:

Platform + BrowserSelected Method
iOS + SafariFIDO
Android + ChromeWebNFC
Mobile App (WebView)FIDO (Proxied via App)
Incompatible EnvironmentsPrompt user to install the United Network App

The priority is set in src/api/sub-utils.ts:

  • Android devices default to nfc.
  • iOS devices and the WebView app default to fido.

Functions

buildFidoRequest(args)

Location: src/api/fido.ts
Constructs the options object for the WebAuthn authentication ceremony.

  • Parameters:
    • hostname: The RP ID (Relying Party Identifier).
    • data: The raw command data (Uint8Array) to be sent to the card.
    • timeout: Optional session timeout.
  • Returns: A PublicKeyCredentialRequestOptions object where the command data is passed as the id in allowCredentials.

buildNfcTagRequest(payload)

Location: src/api/nfc-tag.ts
Prepares the payload for WebNFC transmission.

  • Parameters:
    • payload: The command data (Uint8Array).
  • Behavior: If the payload exceeds 255 bytes, it wraps it with a custom opcode (0xF0) and a length header to handle long commands.

executeNfcSequence(ndefMessage, expectedLength)

Location: src/components/NFCHandlerWindow.vue
Orchestrates a complete command-response cycle over WebNFC.

  1. Write Command: Sends the NDEF record containing the card command.
  2. Write Dummy: Sends a padding record to trigger the card's processing and response.
  3. Read Response: Scans for the response record returned by the card.

Implementation Details

FIDO Implementation

The FIDO flow uses the @simplewebauthn/browser library to interface with the browser's WebAuthn API.

typescript
// Example from NFCHandlerWindow.vue
const fidoRequest = buildFidoRequest({
  hostname: getHost(),
  data: requestData,
});
const response = await startAuthentication(fidoRequest, false);
const signature = await base64url_decode(response.response.signature);

WebNFC Implementation

The WebNFC flow uses the NDEFReader class. Because WebNFC is connectionless, the application must manage the sequence of writes and reads manually.

typescript
// Example from NFCHandlerWindow.vue
const ndef = new window.NDEFReader();
await ndef.scan({ signal: abortController.signal });
ndef.onreading = async () => {
    // Write command record
    // Write dummy record
    // Read response record
};

Usage Example

Generic Card Command Execution

typescript
import { buildFidoRequest } from '@/api/fido';
import { startAuthentication } from '@simplewebauthn/browser';

/**
 * Example of how the application chooses and executes a card command
 */
async function sendCommandToCard(commandData: Uint8Array) {
  const priority = store.state.settings.cardReaderPriority;

  if (priority === 'fido') {
    const options = buildFidoRequest({ hostname: "united.network", data: commandData });
    const assertion = await startAuthentication(options);
    return assertion.response.signature;
  } else if (priority === 'nfc') {
    // WebNFC logic using NDEFReader 
    // ...
  }
}