Skip to content

Repository files navigation

react-native-liveness-kit

npm version license platform

On-device face liveness detection, gesture verification and age estimation for React Native, powered by ONNX Runtime.

100% offline. Every model ships inside the package and is loaded straight from the app bundle. There are no backend servers, no cloud APIs, no network calls, and no model downloads at runtime — the library works with the device in airplane mode. The only network use is optional, one-time, and at build time: when you clone this repo for development you fetch the .onnx files once (see Getting the models); end users who npm install the package get the models pre-bundled and never download anything.

Features

  • 100% on-device & offline — no backend, no APIs, no internet at runtime
  • Models bundled in the package (loaded from the app bundle, never downloaded)
  • ✅ Three components: Liveness, Gesture Verification, Age Estimation
  • TypeScript first (strict: true)
  • Pure React Native CLI — no Expo

Installation

npm install react-native-liveness-kit \
  onnxruntime-react-native \
  react-native-vision-camera \
  react-native-fs \
  @react-native-community/image-editor \
  react-native-image-picker

All of these are peer dependencies — install them in your app. Then, on iOS:

cd ios && pod install && cd ..

No Expo, no Expo modules. The library uses only community React Native CLI packages: react-native-fs (read/copy files), @react-native-community/image-editor (crop/resize) and react-native-image-picker (camera/gallery for age estimation).

Required metro.config.js change

This is the only mandatory configuration step. Add 'onnx' to resolver.assetExts so Metro bundles the models as assets:

// metro.config.js
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');

const config = {
  resolver: {
    assetExts: [...getDefaultConfig(__dirname).resolver.assetExts, 'onnx'],
  },
};

module.exports = mergeConfig(getDefaultConfig(__dirname), config);

You also need camera (and, for age estimation, photo-library) permissions:

  • iOS (Info.plist): NSCameraUsageDescription, NSPhotoLibraryUsageDescription.
  • Android (AndroidManifest.xml): android.permission.CAMERA.

See example/ios/LivenessKitExample/Info.plist and example/android/app/src/main/AndroidManifest.xml for working values.

Usage

Liveness Detection

import { LivenessCheck } from 'react-native-liveness-kit';

<LivenessCheck
  onSuccess={(r) => console.log('Live!', r.confidence)}
  onFailure={(e) => console.warn(e.code, e.message)}
  threshold={0.75}
  requiredFrames={8}
  timeoutMs={30000}
/>;

Gesture Verification

import { GestureVerification, GestureType } from 'react-native-liveness-kit';

<GestureVerification
  referenceImageBase64={enrollmentPhotoBase64}
  gestures={[GestureType.SMILE, GestureType.BLINK, GestureType.OPEN_MOUTH]}
  onSuccess={(r) => console.log('Identity confidence', r.identityConfidence)}
  onFailure={(e) => console.warn(e.code, e.message)}
  onGestureComplete={(g, i) => console.log('done', g, i)}
  identityThreshold={0.7}
/>;

Age Estimation

import { AgeEstimation } from 'react-native-liveness-kit';

<AgeEstimation
  mode="both"
  onResult={(results) => console.log(results)}
  onError={(e) => console.warn(e.code, e.message)}
  onNoFaceDetected={() => console.log('no face')}
/>;

API Reference

<LivenessCheck />

Prop Type Default Description
onSuccess (r: LivenessResult) => void Called when liveness is confirmed.
onFailure (e: LivenessError) => void Timeout / permission / no-face / load error.
onProgress (p: number) => void Progress 0..1.
threshold number 0.75 Min combined "real" score per frame.
requiredFrames number 8 Consecutive passing frames needed.
timeoutMs number 30000 Overall timeout.
style ViewStyle Container style.

<GestureVerification />

Prop Type Default
referenceImageBase64 string
gestures GestureType[] [SMILE, BLINK, OPEN_MOUTH]
onSuccess (r: GestureVerificationResult) => void
onFailure (e: GestureVerificationError) => void
onGestureComplete (g: GestureType, index: number) => void
onProgress (completed: number, total: number) => void
identityThreshold number 0.70
gestureHoldFrames number 4
timeoutPerGestureMs number 15000
randomizeOrder boolean false
style ViewStyle

GestureType: SMILE, BLINK, OPEN_MOUTH, TURN_LEFT, TURN_RIGHT, NOD, RAISE_HAND, THUMBS_UP, PEACE_SIGN. The last three have no embedded hand model and fall back to a motion heuristic (a console warning is emitted); treat them as optional.

<AgeEstimation />

Prop Type Default
mode 'camera' | 'gallery' | 'both' 'both'
onResult (r: AgeEstimationResult[]) => void
onError (e: AgeEstimationError) => void
onNoFaceDetected () => void
confidenceThreshold number 0.55
maxFaces number 5
showPreview boolean true
style ViewStyle

How it works

Every model runs through ONNX Runtime on the device. Camera frames (or a gallery image) are cropped/resized with @react-native-community/image-editor (to PNG), decoded to raw pixels by a small built-in PNG decoder, and normalized to a Float32Array in JavaScript, then fed to the bundled .onnx models. No image, frame, embedding or result ever leaves the phone — there is no network call in the inference path.

Liveness combines two FASNet anti-spoofing models on two crop scales (score = 0.6·model1 + 0.4·model2) and requires several consecutive passing frames. Gesture verification combines geometric landmark analysis (EAR/MAR/head pose) with ArcFace embeddings + cosine similarity to confirm it is the same person from the enrollment photo. Age estimation runs a face detector then the SSR-Net age regressor on a margin-padded square crop of each face.

Models

The bundled set (~118 MB total) actually used by this package:

Model Real source Size I/O
face_detector.onnx YOLOv8n-face (deepghs/yolo-face) 11.6 MB [1,3,640,640][1,5,8400]
face_landmarks.onnx MediaPipe Face Mesh V2 with attention (face_landmarker.task) 4.7 MB [1,256,256,3] NHWC → 478×(x,y,z)
face_embedding.onnx ArcFace MobileFaceNet (immich-app/buffalo_s) 13 MB [1,3,112,112][1,512]
age.onnx FairFace ViT (onnx-community/fairface_age, int8) 83 MB [1,3,224,224][1,9] age buckets
gender.onnx InsightFace genderage (buffalo_l) 1.3 MB [1,3,96,96][1,3] (gender + age)
liveness_1.onnx MiniFASNetV2 (minivision-ai/Silent-Face-Anti-Spoofing) 1.7 MB [1,3,80,80][1,3]
liveness_2.onnx MiniFASNetV1SE (minivision-ai/Silent-Face-Anti-Spoofing) 1.7 MB [1,3,80,80][1,3]

Getting the models

The .onnx binaries are not committed to git (they total ~118 MB; age.onnx alone is 83 MB). How you obtain them depends on how you consume the package:

  • Installing from npm (npm install react-native-liveness-kit): nothing to do. The models are bundled inside the npm tarball, so the package works fully offline right after install — no runtime downloads.

  • Cloning this repo (to run the example or contribute): the models/ folder ships only with a README.md — you must place the seven .onnx files there yourself before building. The easiest way:

    node scripts/download-models.js

    It downloads the four ready-made models straight into models/ (with the correct names) and prints the conversion recipe for the other three.

    Each file must live in models/ with this EXACT name (the package loads them by these paths via require('../../models/<name>.onnx')):

    Save as (in models/) Where to get it
    models/face_detector.onnx ⬇️ deepghs/yolo-face — rename model.onnx
    models/face_embedding.onnx ⬇️ immich-app/buffalo_s — rename model.onnx
    models/age.onnx ⬇️ onnx-community/fairface_age — rename model_quantized.onnx
    models/gender.onnx ⬇️ public-data/insightface — rename genderage.onnx
    models/face_landmarks.onnx 🛠️ No public ONNX — generate it: python scripts/generate_face_landmarks.py (see below)
    models/liveness_1.onnx 🛠️ No public ONNX — generate it: python scripts/generate_liveness.py (see below)
    models/liveness_2.onnx 🛠️ No public ONNX — generate it: python scripts/generate_liveness.py (see below)

Generating the converted models (face_landmarks, liveness_*)

These three have no ready-made ONNX, so two Python scripts build them from their official sources straight into models/. Use Python 3.10–3.12 (TensorFlow and torch have no 3.13 wheels), and a separate venv per script (their deps pin conflicting onnx/numpy versions):

# Face Mesh "with attention" → models/face_landmarks.onnx
#   (downloads MediaPipe's face_landmarker.task and converts tflite → ONNX)
python3.12 -m venv venv-tf
./venv-tf/bin/pip install tensorflow tf2onnx "onnx==1.16.2"
./venv-tf/bin/python scripts/generate_face_landmarks.py

# MiniFASNet anti-spoofing → models/liveness_1.onnx + liveness_2.onnx
#   (clones Silent-Face-Anti-Spoofing and exports its .pth weights → ONNX)
python3.12 -m venv venv-torch
./venv-torch/bin/pip install "numpy<2" torch onnx
./venv-torch/bin/python scripts/generate_liveness.py

💡 Tip: to give contributors a one-click download instead of running the scripts, attach the seven prebuilt .onnx files to a GitHub Release of your fork — Releases hold large binaries without bloating git history.

Maintainers: make sure all seven files are in models/ before npm publishnpm publish packs them from the working dir, so the tarball ships them and end users get everything offline.

Example app

A pure React Native CLI app (no Expo) lives in example/. From the repo root:

npm install
node scripts/download-models.js   # populate models/
npm run build

cd example
npm install
# iOS:
cd ios && pod install && cd ..
npm run android                   # or: npm run ios

See example/README.md for full details. It contains a dark-themed home screen plus dedicated screens for each feature.

License

MIT

About

On-device face liveness, gesture verification and age estimation for React Native using ONNX Runtime

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages