@rootherald/react-native
React Native bindings for Root Herald. Bridges the iOS RootHeraldKit and Android RootHeraldClient native SDKs into a JS-callable API with idiomatic React hooks. Keyless: the client collects a hardware-rooted evidence blob over a backend-issued nonce and hands it back to your app — it holds no secret and never renders a verdict. Your backend relays the blob to the verify API, which appraises it.
This SDK is planned and not yet available. Until it ships, mint attestation tokens on the device and verify them server-side with @rootherald/node (or any available server SDK). The API shown below is the planned surface and may change before release.
Install
npm install @rootherald/react-native
npx pod-install iosOn RN 0.72+, autolinking discovers the iOS Podspec and Android Gradle module automatically. For older RN versions, run react-native link @rootherald/react-native once after install.
Five-line example
import { useCollectEvidence, getOrCreateSharedClient } from '@rootherald/react-native';
// The publishable key is for site/origin attribution only — never a secret.
const client = getOrCreateSharedClient({ publishableKey: 'rh_pk_live_REPLACE_ME' });
export function SignupScreen() {
// getNonce/relay hit YOUR backend, which owns the rh_sk_ secret and the verdict.
const { collect, loading } = useCollectEvidence({
client,
getNonce: () => myBackend.getChallengeNonce('signup'),
relay: (evidence) => myBackend.verifyAndAuthorize('signup', evidence),
});
return <Button onPress={collect} disabled={loading} title="Attest device" />;
}The hook starts idle. When the user taps the button, collect() walks the native bridge into RootHeraldKit (iOS) or RootHeraldClient (Android), collects a hardware-rooted evidence blob over the nonce, and hands it to your relay callback. The verdict is decided by your backend and never travels through the client.
Imperative client
import { RootHeraldClient, NotEnrolledError } from '@rootherald/react-native';
// Keyless collector. The publishable key is optional site attribution, not a secret.
const client = new RootHeraldClient({ publishableKey: 'rh_pk_live_REPLACE_ME' });
// 1. Get a fresh challenge nonce from YOUR backend (it called
// POST /api/v1/attestations/challenge with its rh_sk_ secret).
const nonce = await myBackend.getChallengeNonce('signup');
// 2. Collect a keyless quote-over-nonce evidence blob on-device.
try {
const evidence = await client.collectEvidence(nonce);
// 3. Relay the blob to your backend, which POSTs it to
// /api/v1/attestations/verify and enforces the verdict.
if (await myBackend.verifyAndAuthorize('signup', evidence)) {
proceed();
} else {
refuse();
}
} catch (e) {
if (e instanceof NotEnrolledError) await client.enroll(myBackend.relayEnroll);
}Hook behavior
autoStart: truefirescollect()once on mount.collect()is stable across renders and coalesces overlapping calls, so only the latest result wins.- In-flight calls are cancelled on unmount via
AbortController. reset()clears local state without firing a new collect.
Where the evidence goes
The client opens no socket to RootHerald and carries no secret — the only key it may hold is the rh_pk_ publishable key, used solely for site/origin attribution. It always hands the collected blob back to your own app code, which relays it to your backend. Your backend issues the nonce, POSTs the evidence to POST /api/v1/attestations/verify, and enforces the verdict — all authenticated with its rh_sk_ secret via the server SDK @rootherald/node. The secret must never reach the device.
The bare workflow and Expo development builds are fully supported. The Expo managed workflow / Expo Gocan't load the native attestation code — use a development build on Expo SDK 50+. An Expo config plugin is planned to simplify managed-workflow setup.
Native dependencies
- iOS:
RootHeraldKit(SwiftPM), iOS 14+, App Attest entitlement required. - Android:
io.rootherald:rootherald(Gradle),minSdk26, AndroidKeyStore.
Sample app
A single-screen Expo bare-workflow sample ships alongside the SDK when it's released. It demonstrates the hook, error states, and a sheet to switch between Direct / Custom Domain / Reverse Proxy transport modes.
Bridge architecture
We use the legacy NativeModule pattern rather than a generated TurboModule spec; the package supports RN 0.72+.