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

@intellij-shivam/react-native-biometric

v0.1.0

Published

Biometric authentication for React Native — Face ID, Touch ID, Optic ID, and Android fingerprint/face unlock via BiometricPrompt. Hardware-backed signing (Secure Enclave, StrongBox, Keystore), New Architecture TurboModule, consistent cross-platform crypto

Readme

react-native-biometric

Biometric authentication for React Native — Face ID, Touch ID, Optic ID, and Android fingerprint / face unlock, with hardware-backed cryptographic signing. The maintained, New Architecture successor to the unmaintained react-native-biometrics.

A TurboModule built in Swift and Kotlin on the modern platform APIs (LocalAuthentication + Secure Enclave on iOS, BiometricPrompt + Keystore/StrongBox on Android), with a small, fully typed JavaScript API.

  • New Architecture native. A codegen'd TurboModule — synchronous type-safe bindings, Hermes-ready, no legacy bridge.
  • Identical crypto on both platforms. Public keys are always base64 X.509 SubjectPublicKeyInfo (SPKI) DER; signatures are standard ECDSA-SHA256 (DER) or RSA-PKCS#1-v1.5-SHA256. Verify with OpenSSL, WebCrypto, JCA, or any server-side library — no platform-specific fixups.
  • No authentication bypasses by construction. Signing keys are auth-per-use: the Keystore/Keychain itself refuses to sign without a fresh biometric authorization. There is no code path that returns a signature without a prompt, and no "recently unlocked" grace period.
  • Consistent behavior where possible, documented behavior where not. Key creation is silent on both platforms. Keys invalidate on enrollment change on both platforms. Disabling the passcode fallback works on both platforms. Where the platforms genuinely differ, the difference is documented instead of papered over.
  • One error vocabulary. Both platforms produce the same typed error codes for the same logical condition — userCancel means the same thing everywhere.
  • Cancellation is not an exception. Prompts resolve with a discriminated union ({ success: false, code: 'userCancel' }), so your control flow doesn't live in catch blocks.

📘 New here? The Implementation Guide walks through the two integration flows step by step — UI gating and server-verified biometric login — with complete client and server code, an error-handling cookbook, and simulator/emulator testing instructions.

Requirements

| | Minimum | |---|---| | React Native | 0.76+ (New Architecture) | | iOS | 15.1+ | | Android | API 24+ (Android 7.0) |

Installation

npm install @intellij-shivam/react-native-biometric
# or
yarn add @intellij-shivam/react-native-biometric
cd ios && pod install

iOS

Add a Face ID usage description to your Info.plist (required by iOS whenever Face ID may be used):

<key>NSFaceIDUsageDescription</key>
<string>Authenticate with Face ID to sign in</string>

Android

Nothing to do. The USE_BIOMETRIC permission is merged in automatically from the library manifest.

Quick start

import {
  getBiometrics,
  authenticate,
  createSigningKey,
  sign,
} from '@intellij-shivam/react-native-biometric';

// 1. What can this device do?
const info = await getBiometrics();
// { available: true, enrolled: true, biometryType: 'faceId',
//   securityLevel: 'strong', deviceCredentialEnrolled: true }

// 2. Gate some UI behind a prompt (no cryptography).
const auth = await authenticate({ title: 'Unlock your notes' });
if (auth.success) {
  // auth.authMethod: 'biometric' | 'deviceCredential'
} else if (auth.code !== 'userCancel') {
  showError(auth.code); // typed: 'lockout' | 'notEnrolled' | ...
}

// 3. Or prove it to your server: enroll once…
const { publicKey } = await createSigningKey();
await api.registerBiometricKey(publicKey); // base64 SPKI DER

// …then sign a server challenge on each login.
const challenge = await api.getChallenge();
const result = await sign({ payload: challenge, title: 'Sign in' });
if (result.success) {
  await api.login(challenge, result.signature);
} else if (result.code === 'keyInvalidated') {
  // Biometric enrollment changed → rotate the key.
  await createSigningKey().then((k) => api.registerBiometricKey(k.publicKey));
}

Why not authenticate() for login?

authenticate() only proves something to your JavaScript, which a compromised device can lie to. For anything a server trusts, use createSigningKey() + sign(): the private key never leaves secure hardware (Secure Enclave / StrongBox / TEE), and the OS only produces a signature after a genuine biometric authorization. Your server verifies the signature against the public key it stored at enrollment.

API

getBiometrics(): Promise<BiometricsInfo>

Describes current capabilities. Never prompts, never throws for expected states.

interface BiometricsInfo {
  available: boolean;            // can a prompt be shown right now?
  enrolled: boolean;             // is any biometric enrolled?
  biometryType: 'faceId' | 'touchId' | 'opticId' | 'biometrics' | 'none';
  securityLevel: 'strong' | 'weak' | 'none';  // Android Class 3 / Class 2
  deviceCredentialEnrolled: boolean;          // PIN/pattern/passcode set?
  reason?: 'noHardware' | 'notEnrolled' | 'lockedOut' | 'unavailable' | 'securityUpdateRequired';
}

Why biometryType is just 'biometrics' on Android: Android does not reveal which sensor (fingerprint, face, iris) a prompt will use. Libraries that report "fingerprint" or "face" on Android are guessing from PackageManager hardware flags — a guess that's wrong on multi-sensor devices. We report what the platform actually knows, and securityLevel tells you what matters: whether a Class 3 (strong) sensor is available.

authenticate(options): Promise<AuthenticateResult>

Shows a biometric prompt with no cryptography attached.

const result = await authenticate({
  title: 'Unlock',              // required
  subtitle: 'Use your fingerprint',  // Android
  description: 'Shown as the reason on iOS',
  cancelLabel: 'Not now',
  allowDeviceCredential: false, // default false — reliably biometric-only on BOTH platforms
  securityLevel: 'strong',      // Android: 'weak' also accepts Class 2 face unlock
  confirmationRequired: false,  // Android: explicit tap after passive face/iris auth
});
// { success: true, authMethod: 'biometric' | 'deviceCredential' }
// | { success: false, code: BiometricErrorCode, message: string }
  • To support devices with Class 2 (weak) face unlock, pass securityLevel: 'weak'. Weak biometrics can gate UI but cannot authorize hardware-backed signing (an Android platform rule, not a library limitation).
  • allowDeviceCredential: true adds PIN/pattern/passcode fallback. On iOS this uses a combined policy; when the user taps "Use Passcode" inside that prompt, iOS does not report which method completed, so authMethod reflects the method iOS offered first. Android always reports the exact method.

createSigningKey(options?): Promise<CreateKeyResult>

Creates a hardware-backed key pair. Silent on both platforms — the prompt appears when you sign(), never at creation. Replaces any existing key under the same alias.

const key = await createSigningKey({
  alias: 'login-key',                  // optional, defaults to a library alias
  algorithm: 'ec256',                  // default; or 'rsa2048'
  accessControl: 'biometryCurrentSet', // default; or 'biometryAny' | 'biometryOrDeviceCredential'
  strongBox: 'preferred',              // Android: 'preferred' | 'required' | 'never'
});
// { publicKey: string /* base64 SPKI DER */,
//   algorithm: 'ec256',
//   securityLevel: 'strongBox' | 'secureEnclave' | 'trustedEnvironment' | 'software',
//   insideSecureHardware: boolean }

Throws BiometricError with notEnrolled, passcodeNotSet, or unsupported (e.g. strongBox: 'required' on a device without StrongBox).

Access control:

| accessControl | Usable after | Enrollment change | |---|---|---| | biometryCurrentSet (default) | biometric auth only | key permanently invalidated — both platforms | | biometryAny | biometric auth only | key survives (weaker; choose deliberately) | | biometryOrDeviceCredential | biometric or PIN/pattern/passcode | key survives (platform limitation) |

Algorithm guidance: prefer ec256 (ECDSA P-256). It's the only algorithm the iOS Secure Enclave supports, it's faster, and its keys are smaller. Use rsa2048 only when your backend demands RSA — on iOS, RSA private keys live in the keychain (still auth-gated and reported honestly as securityLevel: 'software'), not the Secure Enclave.

sign(options): Promise<SignResult>

Signs a payload, gated by a biometric prompt.

const result = await sign({
  payload: challenge,        // required
  payloadEncoding: 'utf8',   // or 'base64' for binary payloads
  title: 'Sign in',          // required
  alias: 'login-key',
  cancelLabel: 'Cancel',
  allowDeviceCredential: false, // only honored for biometryOrDeviceCredential keys
});
// { success: true, signature: string /* base64 */, authMethod: 'biometric' | 'deviceCredential' }
// | { success: false, code: BiometricErrorCode, message: string }

Signature formats (identical on both platforms):

| Key | Signature | Verify with | |---|---|---| | ec256 | ECDSA-SHA256, ASN.1 DER (RFC 3279) | SHA256withECDSA, WebCrypto ECDSA (after DER→raw), OpenSSL | | rsa2048 | RSASSA-PKCS1-v1_5 with SHA-256 | SHA256withRSA, WebCrypto RSASSA-PKCS1-v1_5, OpenSSL |

Server-side verification in Node.js:

const { createVerify, createPublicKey } = require('node:crypto');

const key = createPublicKey({
  key: Buffer.from(publicKeyBase64, 'base64'), // straight from createSigningKey()
  format: 'der',
  type: 'spki',
});
const ok = createVerify('sha256')
  .update(challenge)
  .verify(key, Buffer.from(signatureBase64, 'base64'));

getPublicKey(alias?): Promise<string | null> · hasSigningKey(alias?): Promise<boolean> · deleteSigningKey(alias?): Promise<boolean>

Key housekeeping. None of these ever show a prompt. deleteSigningKey resolves true if a key was deleted, false if none existed.

Error codes

Every failure — resolved ({ success: false, code }) or thrown (BiometricError.code) — uses one vocabulary:

| Code | Meaning | |---|---| | userCancel | User dismissed the prompt (cancel button, back gesture, tap outside) | | systemCancel | OS dismissed the prompt (app backgrounded, incoming call, …) | | authenticationFailed | Biometric didn't match after the allowed retries (iOS; Android keeps the prompt open and eventually reports lockout) | | lockout | Too many failed attempts; biometry temporarily locked | | lockoutPermanent | Biometry locked until the user re-authenticates with their device credential | | notEnrolled | No biometric enrolled | | noHardware | No biometric hardware | | passcodeNotSet | No device PIN/pattern/passcode (required for credential fallback and for key creation) | | unavailable | Biometric hardware temporarily unavailable | | keyNotFound | No signing key under that alias | | keyInvalidated | Biometric enrollment changed; delete + recreate the key and re-register the public key | | unsupported | Requested option isn't supported on this device | | unknown | Anything else; message carries the platform detail |

Platform behavior matrix

Where iOS and Android genuinely differ, we document it rather than pretend:

| Behavior | iOS | Android | |---|---|---| | Key creation prompt | silent | silent | | Key invalidated on enrollment change (biometryCurrentSet) | ✅ | ✅ | | Prompt required for every signature | ✅ (Keychain ACL) | ✅ (auth-per-use Keystore key) | | Biometric-only enforcement (allowDeviceCredential: false) | ✅ (fallback button hidden) | ✅ (BIOMETRIC_STRONG only) | | Concrete sensor reported | Face ID / Touch ID / Optic ID | generic biometrics (platform doesn't say) | | Weak (Class 2) biometrics for authenticate | n/a (always strong) | opt-in via securityLevel: 'weak' | | Weak biometrics for sign | n/a | ❌ — Android requires Class 3 for Keystore auth; we fail honestly instead of skipping the prompt | | authMethod accuracy with credential fallback | best-effort (see authenticate) | exact | | RSA keys in secure hardware | ❌ Secure Enclave is EC-only (reported as software) | ✅ TEE/StrongBox |

How it compares

| | react-native-biometric (this) | react-native-biometrics (SelfLender) | expo-local-authentication | react-native-keychain | |---|---|---|---|---| | Maintained | ✅ | ❌ unmaintained | ✅ | ✅ | | New Architecture (TurboModule) | ✅ native | ❌ | ✅ | ✅ | | Hardware-backed signing (Secure Enclave / StrongBox) | ✅ | ✅ (RSA only) | ❌ prompt only | ❌ secret storage, no signing | | Same public-key format on both platforms | ✅ SPKI | ❌ | n/a | n/a | | Prompt required for every signature (no grace period) | ✅ | ❌ bypassable | n/a | n/a | | Key invalidation on enrollment change (both platforms) | ✅ | Android only | n/a | partial | | Typed cross-platform error codes | ✅ | ❌ | partial | ❌ |

Use expo-local-authentication if you only need a prompt inside Expo Go. Use react-native-keychain for storing secrets. Use this library when a server needs to trust the authentication — biometric login, transaction signing, passwordless auth.

Migrating

// Before
const rnBiometrics = new ReactNativeBiometrics();
const { available, biometryType } = await rnBiometrics.isSensorAvailable();
const { success } = await rnBiometrics.simplePrompt({ promptMessage: 'Confirm' });
const { publicKey } = await rnBiometrics.createKeys();
const { signature } = await rnBiometrics.createSignature({ promptMessage: 'Sign in', payload });

// After — no class instance, direct imports
const { available, biometryType } = await getBiometrics();
const { success } = await authenticate({ title: 'Confirm' });
const { publicKey } = await createSigningKey();          // ec256 by default, was RSA
const result = await sign({ title: 'Sign in', payload });

Notes: keys default to ec256 (use algorithm: 'rsa2048' for drop-in server compatibility with old RSA verification code); public keys are SPKI on both platforms (the old library returned raw PKCS#1 on iOS).

| Before | After | |---|---| | isSensorAvailable() | getBiometrics() | | simplePrompt(...) / authenticateWithOptions(...) | authenticate({...}) | | createKeys(alias, type) / createKeysWithType | createSigningKey({ alias, algorithm }) | | signWithOptions / verifyKeySignature | sign({...}) — always prompts, never verifies locally* | | deleteKeys(alias) | deleteSigningKey(alias) | | doesKeyExist / getAllKeys | hasSigningKey(alias) / getPublicKey(alias) |

* Local "verify signature" APIs give a false sense of security — verification belongs on the server that holds the public key. If you need local verification for tests, use the SPKI public key with any crypto library.

Behavioral differences you'll appreciate: public keys are the same format on both platforms; there is no signature path that bypasses the prompt; error codes are identical across platforms; createKeys no longer prompts on Android.

Documentation

Example app

yarn
yarn example ios      # or: yarn example android

The example exercises every API and logs typed results, including error codes for cancel/lockout flows.

Roadmap

  • Biometric enrollment-change events (JS listener, both architectures)
  • Expo config plugin (NSFaceIDUsageDescription automation)
  • Play Integrity / App Attest helpers
  • Key attestation surface (Android attestationChallenge)

License

MIT