Hardware-backed secure storage for React Native. Secrets are encrypted with AES-GCM, gated by biometrics or device credentials, and stored in the system Keychain (iOS/Apple) or Android Keystore โ all behind a simple Promise-based API and first-class React hooks.
Note
Upgrading from v5? See MIGRATION.md. v6 requires the New Architecture (RN 0.76+) and react-native-nitro-modules. Windows support was removed.
- ๐งญ Platform support
- โ๏ธ Installation
- โก๏ธ Quick start
- โ๏ธ React Hooks API
- ๐ API reference
- ๐ก๏ธ Security
- ๐ณ Tree-shaking
- ๐ฎ Example app
- ๐ ๏ธ Development
- ๐ฉบ Troubleshooting
- ๐ค Contributing
- ๐ License
| Platform | Minimum | Notes |
|---|---|---|
| React Native | 0.76.0 | New Architecture + react-native-nitro-modules required. |
| iOS | 13.0 | Add NSFaceIDUsageDescription to Info.plist for biometrics. |
| macOS | 11.0 | Catalyst and native macOS via system keychain. |
| visionOS | 1.0 | Secure Enclave-backed; biometric UX adapts to visionOS. |
| watchOS | 7.0 | Paired-device auth; storage syncs through watchOS keychain. |
| Android | API 23 | StrongBox requires API 28+; falls back to strongest available authenticator. |
npm install react-native-sensitive-info react-native-nitro-modules
# yarn add react-native-sensitive-info react-native-nitro-modules
# pnpm add react-native-sensitive-info react-native-nitro-modulesNo manual linking required โ Nitro handles autolinking.
Run pod install, then add to Info.plist if you use biometric prompts:
<key>NSFaceIDUsageDescription</key>
<string>Face ID is used to unlock secrets stored in the secure enclave.</string>Add to AndroidManifest.xml:
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />Warning
Expo Go does not include native Nitro modules. Use a custom dev client or an EAS build.
Add the plugin to app.json, then run npx expo prebuild --clean:
{
"expo": {
"plugins": ["react-native-sensitive-info"]
}
}For a full Expo walkthrough see EXPO.md.
import { setItem, getItem, deleteItem } from 'react-native-sensitive-info'
// Write โ uses the strongest available security policy by default
await setItem('session-token', 'abc123', { service: 'auth' })
// Read โ returns value + metadata
const item = await getItem('session-token', { service: 'auth' })
console.log(item?.value) // 'abc123'
console.log(item?.metadata.securityLevel) // e.g. 'secureEnclave'
// Remove
await deleteItem('session-token', { service: 'auth' })Building a component? The hooks API handles loading states, cleanup, and error boundaries โ no useEffect boilerplate needed.
import { Text, View, ActivityIndicator } from 'react-native'
import {
useSecureStorage,
useSecurityAvailability,
} from 'react-native-sensitive-info/hooks'
function SecretsView() {
const { items, isLoading, error, saveSecret, removeSecret } =
useSecureStorage({ service: 'myapp', includeValues: true })
const { data: capabilities } = useSecurityAvailability()
if (isLoading) return <ActivityIndicator />
if (error) return <Text>Error: {error.message}</Text>
return (
<View>
{items.map(item => (
<Text key={item.key}>
{item.key}: {item.value} ({item.metadata.securityLevel})
</Text>
))}
<Text>Biometry: {capabilities?.biometry ? 'available' : 'unavailable'}</Text>
</View>
)
}| Hook | Use case |
|---|---|
useSecureStorage() |
List, add, and remove all secrets in a service. |
useSecret() |
Fetch a single secret with read/write mutations. |
useHasSecret() |
Lightweight existence check (no decryption). |
useSecurityAvailability() |
Query device capabilities (auto-cached). |
useKeyRotation() |
Rotate the master key for a service. |
useBiometryStatusWatcher() |
Subscribe to biometric enrollment-state transitions. |
All hooks auto-cancel in-flight requests on unmount. For advanced patterns and best practices see HOOKS.md.
| Method | Description |
|---|---|
setItem(key, value, options?) |
Write a secret using the strongest available security policy. |
getItem(key, options?) |
Read a secret and its metadata. Pass includeValue: false to skip the payload. |
hasItem(key, options?) |
Check whether a secret exists (no biometric prompt). |
deleteItem(key, options?) |
Remove a secret. Returns true if something was deleted. |
getAllItems(options?) |
List all secrets in a service. |
clearService(options?) |
Remove every secret in a service namespace. |
getSupportedSecurityLevels() |
Snapshot of device capabilities (biometrics, Secure Enclave, StrongBox, โฆ). |
canUseAccessControl(policy) |
Check whether a given access-control policy is supported on this device. |
rotateKeys(options?) |
Bump the active key version; subsequent reads transparently re-encrypt older entries. |
Common options:
| Option | Default | Description |
|---|---|---|
service |
bundle ID / 'default' |
Logical namespace for secrets. |
accessControl |
'secureEnclaveBiometry' |
Preferred write policy; native layer picks the strongest supported fallback. |
authenticationPrompt |
โ | Localized strings for biometric / device-credential prompts. |
iosSynchronizable |
false |
Sync via iCloud Keychain. |
keychainGroup |
โ | Custom Keychain access group. |
Errors are typed โ import predicates from react-native-sensitive-info/errors:
import {
isAuthenticationCanceledError,
isKeyInvalidatedError,
isIntegrityViolationError,
} from 'react-native-sensitive-info/errors'Every secret is encrypted with AES-GCM and bound to a hardware-protected key โ the Secure Enclave on Apple platforms and the Android Keystore (StrongBox when available). Each entry carries an HMAC-SHA256 integrity tag recomputed on every read; a mismatch raises IntegrityViolationError before any biometric prompt fires, so spoofed entries can never trigger user authentication.
For the full cryptographic model โ key derivation, AAD binding, replay defense, and threat classification โ see THREAT_MODEL.md.
All entry points are side-effect-free ("sideEffects": false). Import only what you use:
| Subpath | Contents |
|---|---|
react-native-sensitive-info |
Core API (setItem, getItem, hasItem, deleteItem, getAllItems, clearService, rotateKeys, โฆ) |
react-native-sensitive-info/hooks |
React hooks (useSecureStorage, useSecret, useKeyRotation, โฆ) |
react-native-sensitive-info/errors |
Typed error classes and instanceof predicates |
cd example && yarn install
yarn ios # iOS
yarn android # Androidyarn install # Install dependencies
yarn codegen # Regenerate Nitro bindings
yarn typecheck # Type-check sources
yarn build # Build distributable packages- Biometric prompt never appears โ use
canUseAccessControl(policy)to check before writing and fall back todevicePasscodeif needed. authentication failedon simulator โ Secure Enclave and StrongBox are unavailable on simulators. Validate biometric policies on physical hardware.E_AUTH_CANCELEDin error message โ the user dismissed the prompt. Handle gracefully; hook state is not poisoned by cancellations.- Undefined symbol on iOS โ re-run
pod installafter upgrading to v6.
Contributions are welcome!
- ๐ Bugs โ Open an issue with a minimal reproduction. Security vulnerabilities should be reported privately โ see SECURITY.md.
- ๐ก New features โ Open an issue first to align on scope before writing code.
- ๐ Pull requests โ Run
yarn testandyarn typecheckbefore submitting. Keep changes focused and reference the relevant issue.
Please review CODE_OF_CONDUCT.md before contributing.
MIT ยฉ Mateus Andrade