npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ping-identity/rn-binding

v1.0.0

Published

The Binding Module provides native-backed device binding and signing-verifier capabilities for React Native.

Readme

Ping Identity

Ping Identity React Native Binding

This package provides native-backed device binding and signing-verifier capabilities for React Native.

Table of contents

Install

Note: This module requires that the @ping-identity/rn-core module is already set up and installed.

# Install & setup the core module
yarn add @ping-identity/rn-core
# Install the rn-binding module
yarn add @ping-identity/rn-binding
# If you are developing your app using iOS, run this command
cd ios && pod install

Optional integration packages:

yarn add @ping-identity/rn-logger
yarn add @ping-identity/rn-storage

Journey integration

Run Journey binding callbacks explicitly before journey.next(...).

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

const binding = createBindingClient();

if (node.type === 'ContinueNode') {
  for (const callback of node.callbacks ?? []) {
    if (callback.type === 'DeviceBindingCallback') {
      await binding.bindForJourney(journey, { index: 0 });
    }

    if (callback.type === 'DeviceSigningVerifierCallback') {
      await binding.signForJourney(journey, { index: 0 });
    }
  }

  await journey.next({});
}

Logging integration (optional)

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

const binding = createBindingClient({
  logger: logger({ level: 'debug' }),
});

useJourneyForm integration

When using useJourneyForm, pass handledCallbackTypes so binding fields are excluded from blocking submit issues. Run each integration, then submit when form.canSubmit is true.

import { useJourney, useJourneyForm } from '@ping-identity/rn-journey';
import { createBindingClient } from '@ping-identity/rn-binding';
import { nativeExtensionCallbackType } from '@ping-identity/rn-types';

const [node, actions] = useJourney(client);
const form = useJourneyForm(node, {
  handledCallbackTypes: new Set([
    nativeExtensionCallbackType.DeviceBindingCallback,
    nativeExtensionCallbackType.DeviceSigningVerifierCallback,
  ]),
});
const binding = createBindingClient();

for (const field of form.fields) {
  if (field.ref.type === nativeExtensionCallbackType.DeviceBindingCallback) {
    await binding.bindForJourney(journey, { index: field.ref.typeIndex });
  }
  if (
    field.ref.type === nativeExtensionCallbackType.DeviceSigningVerifierCallback
  ) {
    await binding.signForJourney(journey, { index: field.ref.typeIndex });
  }
}

if (form.canSubmit) {
  await actions.next(form.input);
}

Custom UI collectors

Use pinCollector and userKeySelector only when you want app-owned UI. When omitted, the native SDK default UI is used on each platform.

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

const binding = createBindingClient({
  ui: {
    pinCollector: async (prompt: BindingPrompt) => {
      // Show app PIN modal and resolve with the entered PIN.
      return showPinModal(prompt);
    },
    userKeySelector: async (keys: UserKeyOption[]) => {
      // Show app key-selection UI and resolve with the chosen key.
      return showKeySelector(keys);
    },
  },
});

User key storage

By default, key metadata is stored in a shared location. Pass a custom storage handle (created by configureBindingUserKeyStorage from @ping-identity/rn-storage) to isolate key storage per app or configuration.

import { createBindingClient } from '@ping-identity/rn-binding';
import { configureBindingUserKeyStorage } from '@ping-identity/rn-storage';

const binding = createBindingClient({
  userKeyStorage: configureBindingUserKeyStorage({
    android: {
      keyAlias: 'binding_user_keys',
      fileName: 'binding_user_keys',
      strongBoxPreferred: true,
      cacheStrategy: 'no_cache',
    },
    ios: {
      account: 'com.example.binding.keys',
      encryptor: true,
    },
  }),
});

Managing stored keys

After a successful bind, a key record is stored locally on the device. Use these module-level utilities to inspect and clean up stored keys — for example when a user logs out, de-registers a device, or you need to recover from a corrupted binding state.

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

// Retrieve all locally stored binding keys
const keys: UserKeyOption[] = await getAllKeys();
// [{ id: 'abc123', userId: 'user-001', username: 'alice', authenticationType: 'BIOMETRIC_ONLY' }]

// Delete a single key (pass the UserKeyOption returned by getAllKeys)
await deleteKey(keys[0]);

// Delete all locally stored keys
await deleteAllKeys();

These functions are stateless utilities — they do not require a BindingClient instance.

What gets deleted

On both platforms, deleting a key removes:

  • The key metadata record (username, userId, authenticationType) stored in encrypted SharedPreferences (Android) or Keychain (iOS).
  • The cryptographic key material stored in Android Keystore or iOS Secure Enclave.

Deleting a key locally does not remove the device registration record from the server. If you need to deregister the device server-side, use the device-client package first, then call deleteKey to clean up the local key.

Error handling

deleteKey rejects with BINDING_KEY_DELETE_ERROR if the specified key is not found.

try {
  const keys = await getAllKeys();
  if (keys.length > 0) {
    await deleteKey(keys[0]);
  }
} catch (err) {
  if (err instanceof BindingError) {
    // err.code === 'BINDING_KEY_DELETE_ERROR'
  }
}

Advanced configuration example

appPin, biometric, jwt, and signingAlgorithm are per-call options passed directly to bindForJourney or signForJourney. Options marked // iOS only or // Android only are silently ignored on the other platform.

import {
  createBindingClient,
  type BindingPrompt,
  type UserKeyOption,
} from '@ping-identity/rn-binding';
import { configureBindingUserKeyStorage } from '@ping-identity/rn-storage';
import { logger } from '@ping-identity/rn-logger';

const binding = createBindingClient({
  logger: logger({ level: 'debug' }),
  ui: {
    pinCollector: async (prompt: BindingPrompt) => showPinModal(prompt),
    userKeySelector: async (keys: UserKeyOption[]) => showKeySelector(keys),
  },
  userKeyStorage: configureBindingUserKeyStorage({
    android: { keyAlias: 'binding_user_keys', fileName: 'binding_user_keys' },
    ios: { account: 'com.example.binding.keys', encryptor: true },
  }),
});

// Bind — all authenticator options are per-call
await binding.bindForJourney(journey, {
  deviceName: 'My Phone',
  signingAlgorithm: 'RS256', // Android only
  appPin: {
    maxAttempts: 5,
    keystoreType: 'PKCS12', // Android only
    keyTag: 'com.example.apppin.key', // iOS only
    prompt: {
      title: 'Enter PIN',
      subtitle: 'Verify your identity',
      description: 'Enter your application PIN',
    },
  },
  biometric: {
    android: {
      strongBoxPreferred: true, // Android only
      prompt: {
        title: 'Verify identity',
        subtitle: 'Authentication required',
        description: 'Use your biometric to continue',
        negativeButtonText: 'Cancel', // shown for BIOMETRIC_ONLY auth type
      },
    },
    ios: {
      keyTag: 'com.example.biometric.key',
    },
  },
  jwt: {
    issueTimeEpochSeconds: Math.floor(Date.now() / 1000),
    notBeforeTimeEpochSeconds: Math.floor(Date.now() / 1000),
    expirationTimeEpochSeconds: Math.floor(Date.now() / 1000) + 300,
  },
});

// Sign — same options plus claims
await binding.signForJourney(journey, {
  claims: {
    transaction_id: 'txn_abc123',
    amount: '100.00',
  },
  signingAlgorithm: 'RS256', // Android only
  appPin: {
    maxAttempts: 5,
  },
  biometric: {
    android: {
      strongBoxPreferred: true,
    },
  },
  jwt: {
    issueTimeEpochSeconds: Math.floor(Date.now() / 1000),
    expirationTimeEpochSeconds: Math.floor(Date.now() / 1000) + 300,
  },
});

API reference

import {
  createBindingClient,
  getAllKeys,
  deleteKey,
  deleteAllKeys,
} from '@ping-identity/rn-binding';
import type {
  BindingClient,
  BindingConfig,
  BindingJourneyBindOptions,
  BindingJourneySignOptions,
  BindingJourneyResult,
  JourneyInstance,
  UserKeyOption,
} from '@ping-identity/rn-binding';

// Journey binding client
function createBindingClient(config?: BindingConfig): BindingClient;

interface BindingClient {
  bindForJourney(
    journey: JourneyInstance,
    options?: BindingJourneyBindOptions,
  ): Promise<BindingJourneyResult>;
  signForJourney(
    journey: JourneyInstance,
    options?: BindingJourneySignOptions,
  ): Promise<BindingJourneyResult>;
}

// Key management utilities (no client instance required)
function getAllKeys(): Promise<UserKeyOption[]>;
function deleteKey(key: UserKeyOption): Promise<void>;
function deleteAllKeys(): Promise<void>;

type UserKeyOption = {
  id: string; // Unique key identifier (kid)
  userId: string; // User identifier associated with this key
  username: string; // Human-readable username associated with this key
  authenticationType: string; // e.g. "BIOMETRIC_ONLY", "APPLICATION_PIN", "NONE"
};

Configuration options

Client config (createBindingClient)

The client holds only stateful, shared concerns. Per-operation options are passed directly to bindForJourney / signForJourney.

| Option | Type | Description | | -------------------- | ---------------------------------- | --------------------------------------------------------- | | logger | LoggerInstance | JS logger from @ping-identity/rn-logger. | | ui.pinCollector | (prompt) => Promise<string> | App-owned PIN collection UI callback. | | ui.userKeySelector | (keys) => Promise<UserKeyOption> | App-owned user-key selection callback. | | userKeyStorage | UserKeyStorage | Handle returned by configureBindingUserKeyStorage(...). |

Bind options (bindForJourney)

| Option | Type | Default | Description | | ------------------ | ---------------------------- | ----------------- | ---------------------------------------------------------------------------- | | index | number | 0 | Callback index when multiple bind callbacks exist. | | deviceName | string | Device model name | Human-readable device name sent to the server. | | signingAlgorithm | string | "RS512" | JWS algorithm for the signed JWT proof. Android only; iOS always uses ES256. | | appPin | BindingAppPinConfig | — | App PIN authenticator options. | | biometric | BindingBiometricBindConfig | — | Biometric authenticator options. | | jwt | BindingJwtConfig | — | JWT proof timing options. |

Sign options (signForJourney)

| Option | Type | Default | Description | | ------------------ | ---------------------------- | --------- | ---------------------------------------------------------------------------- | | index | number | 0 | Callback index when multiple sign callbacks exist. | | claims | Record<string, unknown> | {} | Custom claims added to the signed JWT payload. | | signingAlgorithm | string | "RS512" | JWS algorithm for the signed JWT proof. Android only; iOS always uses ES256. | | appPin | BindingAppPinConfig | — | App PIN authenticator options. | | biometric | BindingBiometricSignConfig | — | Biometric authenticator options. | | jwt | BindingJwtConfig | — | JWT proof timing options. |

Reserved claim names (sub, exp, iat, nbf, iss, challenge) cannot be used in claims.

BindingAppPinConfig

| Option | Type | Platform | Description | | -------------------- | -------- | ------------------- | ---------------------------------------------------------------------------------------------------- | | maxAttempts | number | Both | Maximum PIN retry count. | | prompt.title | string | Both | PIN prompt title override. | | prompt.subtitle | string | Both | PIN prompt subtitle override. | | prompt.description | string | Both | PIN prompt description override. | | keyTag | string | iOS only, bind only | Keychain key tag for the app PIN authenticator key. Stored at bind time; used automatically on sign. | | keystoreType | string | Android only | KeyStore type for app PIN key storage. Defaults to "PKCS12". |

BindingBiometricBindConfig / BindingBiometricSignConfig

| Option | Type | Platform | Description | | ----------------------------------- | --------- | -------------- | ------------------------------------------------------------------------------------------------------ | | android.prompt.title | string | Android | Biometric prompt title. | | android.prompt.subtitle | string | Android | Biometric prompt subtitle. | | android.prompt.description | string | Android | Biometric prompt description. | | android.prompt.negativeButtonText | string | Android | Cancel button label (shown for BIOMETRIC_ONLY). | | android.strongBoxPreferred | boolean | Android | Prefer StrongBox-backed key storage. Defaults to false. | | ios.keyTag | string | iOS, bind only | Keychain key tag for the biometric authenticator key. Stored at bind time; used automatically on sign. |

BindingJwtConfig

| Option | Type | Platform | Description | | ---------------------------- | -------- | -------- | ------------------------ | | issueTimeEpochSeconds | number | Both | Fixed iat claim value. | | notBeforeTimeEpochSeconds | number | Both | Fixed nbf claim value. | | expirationTimeEpochSeconds | number | Both | Fixed exp claim value. |

Errors

Rejected promises throw a BindingError instance, which extends PingError extends Error. Use instanceof BindingError to narrow in catch blocks.

Stable error codes:

| Code | Description | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | BINDING_ERROR | Unexpected error. | | BINDING_BIND_ERROR | Bind operation failed. | | BINDING_SIGN_ERROR | Sign operation failed. | | BINDING_CANCELLED | User cancelled the authentication prompt, or the operation timed out. | | BINDING_UNSUPPORTED_DEVICE | Device lacks the hardware or OS capabilities required for binding. | | BINDING_NOT_REGISTERED | No local key found for this user on this device — device must be bound first. | | BINDING_KEY_INVALIDATED | The signing key on this device is no longer usable — typically caused by a biometric enrollment change. The user must re-bind; their account and other devices are unaffected. | | BINDING_AUTH_FAILED | Authentication failed — wrong fingerprint, wrong PIN, or biometric lockout. The key is intact and the operation can be retried. | | BINDING_UI_UNAVAILABLE | No foreground Activity (Android) or UIWindowScene (iOS) available. | | BINDING_CALLBACK_NOT_FOUND | No matching Journey callback found for the given journey id and index. | | BINDING_INVALID_CONFIG | Reserved claim names were used in claims, or another configuration error. | | BINDING_KEY_DELETE_ERROR | deleteKey could not find the specified key in local storage. |

Handling key invalidation

BINDING_KEY_INVALIDATED and BINDING_NOT_REGISTERED both mean the key on this device is gone — the user must re-bind. The user's account and any keys bound on other devices are unaffected.

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

try {
  await binding.signForJourney(journey);
} catch (err) {
  if (
    err instanceof BindingError &&
    (err.code === 'BINDING_KEY_INVALIDATED' ||
      err.code === 'BINDING_NOT_REGISTERED')
  ) {
    // Key is gone on this device — clean up and re-bind.
    const keys = await getAllKeys();
    const stale = keys.find((k) => k.userId === currentUserId);
    if (stale) await deleteKey(stale);
    // redirect user to bind flow
  }
}

Platform notes

  • Android requires a foreground Activity for binding and signing calls.
  • iOS requires an active UIWindowScene/ASPresentationAnchor for binding and signing calls.
  • Journey callback execution follows native SDK behavior on both platforms.
  • Key material generated during binding never leaves the device.
  • PIN prompt sequencing is controlled by native SDK flow behavior. Prompt counts and prompt semantics may differ by platform.

Built-in authentication UI

The package ships with the native SDK's default UI for each authenticator type:

| Authenticator (server-configured) | UI shown | | --------------------------------- | ---------------------------------------------------------------------- | | BIOMETRIC_ONLY | System biometric prompt (Android BiometricPrompt / iOS LAContext). | | BIOMETRIC_ALLOW_FALLBACK | Biometric prompt with device-credential fallback. | | APPLICATION_PIN | SDK-built PIN entry dialog. | | NONE | No UI; keys are generated without user prompt. |

When multiple registered keys exist for a signForJourney operation, the SDK presents a built-in picker dialog so the user can choose which key to sign with.

The authenticator type is configured on the AM Journey node, not from JavaScript.

Security notes

  • Private keys are stored in platform-secure enclaves (Android Keystore / iOS Secure Enclave) and never exported.
  • Biometric gating is enforced at the OS level when the biometric authenticator is selected.
  • Signed JWT proofs are generated on-device and submitted as part of the Journey callback flow.

License

This project is licensed under the MIT License - see the LICENSE file for details