---
title: Binding keys to a device in React Native
description: Use the React Native binding module to bind cryptographic keys to a device, verify bound keys during journeys, and manage locally stored key metadata
component: orchsdks
page_id: orchsdks:journey:use-cases/device-binding/react-native-device-binding
canonical_url: https://developer.pingidentity.com/orchsdks/journey/use-cases/device-binding/react-native-device-binding.html
llms_txt: https://developer.pingidentity.com/orchsdks/llms.txt
docs_for_agents: https://developer.pingidentity.com/build-with-ai/docs-for-agents.md
revdate: Fri, 05 Jun 2026 14:22:33 +0100
keywords: ["Device", "Hardware", "Source Code", "Integration", "SDK", "React Native"]
section_ids:
  before_you_begin: Before you begin
  securing_access_to_the_keys: Securing access to the keys
  installing_modules: Installing modules
  create_the_binding_client: Create the binding client
  configuration_options: Configuration options
  binding_keys_to_a_device: Binding keys to a device
  bindforjourney_options: bindForJourney options
  collecting_a_device_name_from_the_user: Collecting a device name from the user
  verifying_bound_keys_on_a_device: Verifying bound keys on a device
  signforjourney_options: signForJourney options
  adding-custom-claims-when-signing: Adding custom claims when signing
  implementing_the_ui_for_pin_and_key_selection: Implementing the UI for PIN and key selection
  providing-a-custom-pin-collector: Providing a custom PIN collector
  bindingprompt_properties: BindingPrompt properties
  example_pin_collector_component: Example PIN collector component
  providing-a-custom-key-selector: Providing a custom key selector
  userkeyoption_properties: UserKeyOption properties
  managing_locally_stored_keys: Managing locally stored keys
  listing_all_keys: Listing all keys
  deleting_a_single_key: Deleting a single key
  deleting_all_keys: Deleting all keys
  key_management_functions: Key management functions
  handling_errors: Handling errors
  error_codes: Error codes
---

# Binding keys to a device in React Native

[icon: circle-check, set=far]PingOne Advanced Identity Cloud [icon: circle-check, set=far]PingAM [icon: react, set=fab]React Native

The Device Binding module provides secure device registration and authentication capabilities for React Native applications. It enables applications to bind cryptographic keys to a device and restrict access to those keys using biometrics, a PIN, or other additional authentication methods.

## Before you begin

You need to create an authentication journey in your server using the appropriate nodes to enable device binding.

The nodes you can use for device binding Journeys include the follows:

* [Device Binding node](https://docs.pingidentity.com/auth-node-ref/latest/device-binding.html)

  Allows users to register one or more devices to their account. A user can bind multiple devices, and each device can be bound to multiple users.

  The client receives a `DeviceBindingCallback` when reaching this node in a journey.

* [Device Signing Verifier node](https://docs.pingidentity.com/auth-node-ref/latest/device-signing-verifier.html)

  Verifies possession of a registered bound device.

  The node requires the client device to sign a challenge string using the private key that corresponds to the public key stored on the server during initial binding.

  The client receives a `DeviceSigningVerifierCallback` when reaching this node in a journey.

* [Device Binding Storage node](https://docs.pingidentity.com/auth-node-ref/latest/device-binding-storage.html)

  Optionally persists collected device binding data to a user's profile in the identity store.

  By default, the **Device Binding node** stores device data in the user's profile. You can choose instead to store the device data in transient state, perhaps to run a custom script to extract additional context.

  In this case, you can use a **Device Binding Storage node** to store the data in the user's profile.

  This node runs entirely server-side, and doesn't send a callback to the client.

## Securing access to the keys

The Device Binding module supports four distinct methods for accessing the private key, each offering different levels of security and user experience.

You specify which authentication type your client uses in the configuration of the **Device Binding node**. To change the authentication type to access the keys, you'll need to rebind the client device

Supported authentication types to access bound keys

* Biometric Only

* Biometric with Fallback

* Application PIN

* No Authentication

|                     |                                                                                                                                                                                                                                                                                                                                   |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Type name           | `BIOMETRIC_ONLY`                                                                                                                                                                                                                                                                                                                  |
| Description         | Requires strict biometric authentication with no fallback options                                                                                                                                                                                                                                                                 |
| Security level      | **High**                                                                                                                                                                                                                                                                                                                          |
| User experience     | Streamlined for devices with reliable biometric sensors                                                                                                                                                                                                                                                                           |
| Behavior            | - Only accepts biometric authentication, such as a fingerprint, face recognition, or an iris scan

- Fails immediately if biometric authentication is unavailable or unsuccessful

- No option to fall back to device PIN, pattern, or password

- Ideal for high-security applications where biometric verification is mandatory |
| Use cases           | Financial applications, enterprise security, medical applications                                                                                                                                                                                                                                                                 |
| Device requirements | Must have functional biometric sensors and enrolled biometric data                                                                                                                                                                                                                                                                |

|                     |                                                                                                                                                                                                                                                                                                                                              |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Type name           | `BIOMETRIC_ALLOW_FALLBACK`                                                                                                                                                                                                                                                                                                                   |
| Description         | Prefers biometric authentication but allows fallback to device credentials                                                                                                                                                                                                                                                                   |
| Security level      | **Medium** to **High**                                                                                                                                                                                                                                                                                                                       |
| User experience     | Flexible with multiple authentication options                                                                                                                                                                                                                                                                                                |
| Behavior            | * The primary method is a biometric authentication, such as a fingerprint, face recognition, or an iris scan

* If biometric authentication fails or is unavailable, users can use device credentials

  * Device credentials include a PIN, a pattern, or a password set at the system level

* Provides better accessibility and usability |
| Use cases           | Consumer applications, general-purpose authentication, accessibility-focused apps                                                                                                                                                                                                                                                            |
| Device requirements | - Biometric sensors preferred, but not required

- Must configure the device lock screen                                                                                                                                                                                                                                                     |

|                     |                                                                                                                                                                                                                                                                                                 |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Type name           | `APPLICATION_PIN`                                                                                                                                                                                                                                                                               |
| Description         | Requires a custom PIN that the application manages entirely                                                                                                                                                                                                                                     |
| Security level      | **Medium**                                                                                                                                                                                                                                                                                      |
| User experience     | Consistent across all devices regardless of hardware capabilities                                                                                                                                                                                                                               |
| Behavior            | * Uses an application-specific PIN separate from device credentials

  * The application collects the PIN through a custom UI

  * The application securely stores PIN data using encrypted storage mechanisms

  * Independent of device biometric capabilities or system-level authentication |
| Use cases           | - Devices without biometric capabilities

- Applications requiring custom authentication flows

- Scenarios where users prefer PIN over biometric authentication

  * Cross-platform consistency requirements                                                                                   |
| Device requirements | None - works on all devices                                                                                                                                                                                                                                                                     |

|                         |                                                                                                                                                                                                                     |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Type name               | `NONE`                                                                                                                                                                                                              |
| Description             | No user authentication required to access cryptographic keys                                                                                                                                                        |
| Security Level          | **Low**                                                                                                                                                                                                             |
| User Experience         | Seamless with no authentication prompts                                                                                                                                                                             |
| Behavior                | * Users can access keys immediately without any verification

* No authentication prompts or delays

* Cryptographic operations proceed without user interaction

* Relies solely on device possession for security |
| Use cases               | - Applications with alternative security measures                                                                                                                                                                   |
| Security Considerations | Anyone with device access can use the cryptographic keys                                                                                                                                                            |
| Device Requirements     | None                                                                                                                                                                                                                |

## Installing modules

To install the module into your React Native project, use `yarn` or `npm` as follows:

* yarn

* npm

```shell
yarn add @ping-identity/rn-binding
```

```shell
npm install @ping-identity/rn-binding
```

For Journey-integrated collection, also install the Journey module if you have not already done so:

* yarn

* npm

```shell
yarn add @ping-identity/rn-journey
```

```shell
npm install @ping-identity/rn-journey
```

## Create the binding client

You create a binding client by calling `createBindingClient()`, which creates the client once at app startup or when configuring your journey integrations. You can reuse it for the lifetime of the app:

Creating the device binding client

```typescript
import { createBindingClient } from '@ping-identity/rn-binding';
import { logger } from '@ping-identity/rn-logger';

const bindingLogger = logger({ level: 'debug' });

const bindingClient = createBindingClient({
  logger: bindingLogger,
});
```

### Configuration options

| Option               | Required | Description                                                                                                                                                                                                                                            |
| -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `logger`             | No       | A `LoggerInstance` created by `logger({ level })` from `@ping-identity/rn-logger`.Provides diagnostic output for binding operations.Learn more in [Configuring logging in React Native](../../customization/logging/react-native-custom-logging.html). |
| `ui.pinCollector`    | No       | A function `(prompt: BindingPrompt) => Promise<string>` that collects a PIN from the user.Replaces the default native PIN dialog. See [Providing a custom PIN collector](#providing-a-custom-pin-collector).                                           |
| `ui.userKeySelector` | No       | A function `(keys: UserKeyOption[]) => Promise<UserKeyOption>` that presents available keys for the user to select.Replaces the default native key selector. See [Providing a custom key selector](#providing-a-custom-key-selector).                  |
| `userKeyStorage`     | No       | A storage handle for persisting bound key metadata.Useful for sharing key state across app sessions.                                                                                                                                                   |

## Binding keys to a device

When a journey node contains a **Device Binding** step, the journey callback type is `DeviceBindingCallback`.

Call `bindingClient.bindForJourney()` to generate a cryptographic key pair, authenticate the user, and register the public key with the server:

Binding cryptographic keys to a device during a journey

```typescript
import { createBindingClient } from '@ping-identity/rn-binding';
import { useJourney } from '@ping-identity/rn-journey';

const bindingClient = createBindingClient();
const [node, actions] = useJourney(journeyClient);

async function handleDeviceBinding(deviceName?: string) {
  await bindingClient.bindForJourney(journeyClient, {
    deviceName,   // Optional: custom label for this device
  });

  // The callback has been fulfilled — advance the journey
  await actions.next();
}
```

The binding process performs the following steps:

1. Validates device support for the requested authentication type.

2. Removes any existing keys for the user on this device.

3. Generates a new cryptographic key pair.

4. Authenticates the user (biometric, PIN, or none, depending on server configuration).

5. Creates a signed proof-of-possession JWT.

6. Stores user key metadata locally.

### `bindForJourney` options

| Option             | Required | Description                                                                                                                                                                    |
| ------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `index`            | No       | Zero-based index of the `DeviceBindingCallback` within the node, when a node contains multiple binding callbacks. Defaults to `0`.                                             |
| `deviceName`       | No       | A human-readable label for the device shown in the server's registered devices list. Defaults to the device model name when omitted.                                           |
| `signingAlgorithm` | No       | Override the signing algorithm for Android devices. It uses the server-configured default when omitted. This property is ignored by the iOS SDK, which uses ES256 exclusively. |
| `appPin`           | No       | A `BindingAppPinConfig` object that configures the pin dialog behavior.                                                                                                        |
| `biometric`        | No       | Override biometric authentication parameters.                                                                                                                                  |
| `jwt`              | No       | Override JWT generation parameters.                                                                                                                                            |

### Collecting a device name from the user

In a profile management journey you may want to let the user choose a name for their device. Detect the `DeviceBindingCallback` field in `useJourneyForm` and render a text input for the device name:

Collecting a device name from the user during binding

```typescript
import { useJourneyForm } from '@ping-identity/rn-journey';

const form = useJourneyForm(node);

const bindingField = form.getFieldByType('DeviceBindingCallback');

// The field has executionMode: 'integration_required'
// Render a text input and pass the entered value to bindForJourney

async function handleBind(enteredDeviceName: string) {
  await bindingClient.bindForJourney(journeyClient, {
    index: bindingField?.ref.typeIndex ?? 0,
    deviceName: enteredDeviceName.trim() || undefined,
  });
  await actions.next();
}
```

|   |                                                                                                                                                                                                                                                                                                                                       |
| - | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | `DeviceBindingCallback` has `executionMode: 'integration_required'` but also exposes a device-name input field that is visible to the user. It is not auto-forwarded by default — the user must confirm the device name before binding proceeds. Contrast this with `DeviceSigningVerifierCallback`, which is fully auto-forwardable. |

## Verifying bound keys on a device

When a journey node contains a **Device Signing Verifier** step, the journey callback type is `DeviceSigningVerifierCallback`. Call `bindingClient.signForJourney()` to sign the server-provided challenge using the locally stored private key:

Signing a server challenge with a bound device key

```typescript
async function handleDeviceSigning() {
  await bindingClient.signForJourney(journeyClient);

  // The callback has been fulfilled — advance the journey
  await actions.next();
}
```

The signing process performs the following steps:

1. Validates any custom claims provided

2. Looks up the appropriate user key for the current user

3. Authenticates the user (biometric, PIN, or none)

4. Signs the server challenge with the private key

5. Creates a verification JWT

### `signForJourney` options

| Option             | Required | Description                                                                                                                        |
| ------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `index`            | No       | Zero-based index of the `DeviceSigningVerifierCallback` within the node. Defaults to `0`.                                          |
| `claims`           | No       | Custom key-value pairs added to the verification JWT. See [Adding custom claims when signing](#adding-custom-claims-when-signing). |
| `signingAlgorithm` | No       | Override the signing algorithm.                                                                                                    |
| `appPin`           | No       | Pre-supply an application PIN string.                                                                                              |
| `biometric`        | No       | Override biometric authentication parameters.                                                                                      |
| `jwt`              | No       | Override JWT generation parameters.                                                                                                |

### Adding custom claims when signing

When signing the server challenge you can add custom data to the resulting JWT. The server can use these claims for context or auditing:

Adding custom claims to the signing JWT

```typescript
await bindingClient.signForJourney(journeyClient, {
  claims: {
    amount: '100.00',
    recipient: 'babs@example.com',
    currency: 'USD',
  },
});
```

|   |                                                                                                                                                                                               |
| - | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | You cannot use reserved JWT claim names (`sub`, `exp`, `iat`, `iss`, `nbf`, and `challenge`) as custom claim keys. Doing so causes the operation to fail with an `invalidCustomClaims` error. |

## Implementing the UI for PIN and key selection

The Device Binding module does not include built-in PIN or key-selection screens, so you must supply your own UI through two functions passed in the `ui` object of `createBindingClient`. The `pinCollector` function is required whenever the server configures `APPLICATION_PIN` authentication. The `userKeySelector` function handles the parallel `RNPingBinding_UserKeyRequired` event that fires when multiple users have bound keys on the same device.

### Providing a custom PIN collector

When the server configures `APPLICATION_PIN` authentication, the native SDK emits a `RNPingBinding_PinRequired` event requesting a PIN from the user. You must supply a `pinCollector` function in `createBindingClient` configuration because the module does not have a built-in PIN UI.

The `pinCollector` receives a `BindingPrompt` that is populated with configuration values from the server, and must return a `Promise<string>` that resolves to the PIN the user entered, or rejects to cancel the operation:

Providing a custom PIN collector to the binding client

```typescript
import { createBindingClient } from '@ping-identity/rn-binding';
import type { BindingPrompt } from '@ping-identity/rn-binding';

// Simple imperative promise resolver — replace with your UI implementation
let _resolvePinPromise: ((pin: string) => void) | null = null;
let _rejectPinPromise: (() => void) | null = null;

const bindingClient = createBindingClient({
  ui: {
    pinCollector: async (prompt: BindingPrompt): Promise<string> => {
      // Show your PIN modal here; resolve when the user submits
      return new Promise((resolve, reject) => {
        _resolvePinPromise = resolve;
        _rejectPinPromise = reject;
        showPinModal(prompt);  // your UI function
      });
    },
  },
});
```

#### `BindingPrompt` properties

| Property      | Type     | Description                                                               |
| ------------- | -------- | ------------------------------------------------------------------------- |
| `title`       | `string` | Modal heading. Typically `"Enter PIN"` or similar server-configured text. |
| `subtitle`    | `string` | Secondary heading line.                                                   |
| `description` | `string` | Instructional text shown below the heading.                               |

#### Example PIN collector component

Calling `pinCollector` bypasses the native default PIN dialog and calls your implementation instead. The prompt argument supplies `title`, `subtitle`, and `description` strings sourced from the server-side journey callback.

Implementing a PIN collector component

```typescript
  import { createBindingClient } from '@ping-identity/rn-binding';
  import type { BindingPrompt } from '@ping-identity/rn-binding';

  const pinCollector = (prompt: BindingPrompt): Promise<string> => {
    // prompt.title       - dialog title from the server callback
    // prompt.subtitle    - dialog subtitle from the server callback
    // prompt.description - dialog description from the server callback
    return new Promise((resolve, reject) => {
      // present your own PIN input UI here
      // call resolve(pin) with the entered PIN string
      // call reject() if the user cancels
    });
  };

  export const bindingClient = createBindingClient({
    ui: {
      pinCollector,
    },
  });
```

### Providing a custom key selector

When multiple users have bound keys on the same device, the native SDK emits a `RNPingBinding_UserKeyRequired` event to ask the user which key to use. You can supply a `userKeySelector` function to replace React Native's default behavior, which delegates key selection to the native platform UI:

Providing a custom key selector for multi-user devices

```typescript
import { createBindingClient } from '@ping-identity/rn-binding';
import type { UserKeyOption } from '@ping-identity/rn-binding';

const bindingClient = createBindingClient({
  ui: {
    userKeySelector: async (keys: UserKeyOption[]): Promise<UserKeyOption> => {
      // Show a list of keys to the user and resolve with the selected one.
      // Reject or throw to cancel the operation.
      return showKeySelectionSheet(keys);
    },
  },
});
```

#### `UserKeyOption` properties

| Property             | Type     | Description                                                                                     |
| -------------------- | -------- | ----------------------------------------------------------------------------------------------- |
| `id`                 | `string` | Unique key identifier (kid) used for deletion.                                                  |
| `userId`             | `string` | The user identifier associated with this key.                                                   |
| `username`           | `string` | Human-readable username shown in selection UI.                                                  |
| `authenticationType` | `string` | Authentication type configured for this key (for example, `BIOMETRIC_ONLY`, `APPLICATION_PIN`). |

## Managing locally stored keys

The module exposes three key-management functions that operate on locally stored binding key metadata. These are useful for building device management screens.

### Listing all keys

Listing all locally stored binding keys

```typescript
import { getAllKeys } from '@ping-identity/rn-binding';

const keys = await getAllKeys();
// Returns UserKeyOption[]
keys.forEach((key) => {
  console.log(`${key.username} (${key.authenticationType}) — id: ${key.id}`);
});
```

### Deleting a single key

Deleting a single binding key by UserKeyOption

```typescript
import { deleteKey } from '@ping-identity/rn-binding';

await deleteKey(key);   // key is a UserKeyOption returned by getAllKeys()
```

### Deleting all keys

Deleting all locally stored binding keys

```typescript
import { deleteAllKeys } from '@ping-identity/rn-binding';

await deleteAllKeys();
```

### Key management functions

Using key management functions together

```typescript
  import {
    getAllKeys,
    deleteKey,
    deleteAllKeys,
  } from '@ping-identity/rn-binding';
  import type { UserKeyOption } from '@ping-identity/rn-binding';

  /**
   * Retrieves all device binding keys stored locally on the device.
   * Returns an array of UserKeyOption, each with id, userId, username,
   * and authenticationType fields.
   */
  export const fetchAllKeys = async (): Promise<UserKeyOption[]> => {
    return await getAllKeys();
  };

  /**
   * Deletes a single device binding key by its userId and id.
   * Pass a UserKeyOption obtained from getAllKeys().
   */
  export const removeKey = async (key: UserKeyOption): Promise<void> => {
    await deleteKey(key);
  };

  /**
   * Deletes all locally stored device binding keys.
   * Use with caution — this cannot be undone and will require
   * the user to re-bind their device.
   */
  export const removeAllKeys = async (): Promise<void> => {
    await deleteAllKeys();
  };
```

## Handling errors

`bindForJourney` and `signForJourney` both reject with a `BindingError` when the native operation fails.

Catching and handling BindingError from journey operations

```typescript
import { BindingError } from '@ping-identity/rn-binding';

try {
  await bindingClient.bindForJourney(journeyClient, { deviceName });
} catch (err) {
  if (err instanceof BindingError) {
    console.error(`[${err.code}] ${err.message}`);
    handleBindingError(err.code);
  } else {
    throw err;
  }
}
```

### Error codes

| Error code              | Cause                                                                                                                                       | Remediation                                                                                                |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `UNSUPPORTED`           | The device lacks required capabilities, or the user has not enrolled biometrics.                                                            | Retry with an alternative authentication type, or prompt the user to enroll biometrics in device settings. |
| `CLIENT_NOT_REGISTERED` | No keys are available for signing. The device has not been bound, or the user removed the authentication method protecting the private key. | Redirect the user to run a binding journey to register a new key.                                          |
| `TIMEOUT`               | The operation exceeded the configured timeout.                                                                                              | Allow retry, optionally with a longer timeout.                                                             |
| `INVALID_CUSTOM_CLAIMS` | Reserved claim names (`sub`, `exp`, `iat`, `iss`, and so on) were used in `claims`.                                                         | Remove or rename the conflicting claim keys.                                                               |
| `ABORT`                 | The user cancelled the operation, for example by tapping **Cancel** on the biometric prompt.                                                | Handle gracefully without showing an error — the user chose not to continue.                               |
| `UNAUTHORIZE`           | The user provided invalid credentials, for example an incorrect PIN.                                                                        | Allow retry and prompt for the correct credentials.                                                        |
