react-native-liveness-kit
v0.1.0
Published
On-device face liveness, gesture verification and age estimation for React Native using ONNX Runtime
Maintainers
Readme
react-native-liveness-kit
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
.onnxfiles once (see Getting the models); end users whonpm installthe 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-pickerAll 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 aREADME.md— you must place the seven.onnxfiles there yourself before building. The easiest way:node scripts/download-models.jsIt 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 viarequire('../../models/<name>.onnx')):| Save as (in
models/) | Where to get it | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | |models/face_detector.onnx| ⬇️ deepghs/yolo-face — renamemodel.onnx| |models/face_embedding.onnx| ⬇️ immich-app/buffalo_s — renamemodel.onnx| |models/age.onnx| ⬇️ onnx-community/fairface_age — renamemodel_quantized.onnx| |models/gender.onnx| ⬇️ public-data/insightface — renamegenderage.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
.onnxfiles to a GitHub Release of your fork — Releases hold large binaries without bloating git history.
Maintainers: make sure all seven files are in
models/beforenpm publish—npm publishpacks 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 iosSee example/README.md for full details. It contains a
dark-themed home screen plus dedicated screens for each feature.
License
MIT
