Skip to main content

Introduction

What @apexnative/keychain is, what it promises on both platforms, and which apps it fits.

@apexnative/keychain keeps an app's secrets where the platform keeps its own: in the Keychain iOS and under keys of the Android Keystore Android. Tokens, passwords and encryption keys go in by key and come out by key; a value can be locked behind Face ID, a fingerprint or the device passcode, and the operating system checks it.

session.ts
import { AccessControl, createKeychain, keychain } from '@apexnative/keychain';

// The default keychain: no prompt, readable in the background.
await keychain.setItem('accessToken', accessToken);

// A vault that opens with the biometrics enrolled today.
const vault = createKeychain({
service: 'vault',
accessControl: AccessControl.BiometryCurrentSet,
prompt: { title: 'Unlock your account' },
});

await vault.setItem('refreshToken', refreshToken); // no prompt
const token = await vault.getItem('refreshToken'); // Face ID, then the value

The write never shows a prompt. The read shows the system prompt, and the value comes back only when the user passes it. The Quick start builds this step by step.

Why another keychain package​

React Native has two common answers for secure storage, and both leave gaps.

react-native-keychain is the standard, but its last release is from March 2025 and its architecture predates the New Architecture: the TurboModule spec types every parameter as Object, and Android still runs a separate old-architecture module. Its API is built around one username and password per service, so a token or a key has to borrow the service as its name. On Android, a request for biometrics on a device without them quietly stores the value without protection.

expo-secure-store has the friendlier key/value model, but one switch for security (requireAuthentication), errors without codes, a changed fingerprint set that reads as null, and a synchronous API that can block the JS thread on a prompt.

This package keeps the key/value model, adds bytes and credentials, and makes the security options mean the same thing on both platforms.

Standardreact-native-keychain 10
Expoexpo-secure-store
@apexnative · this librarykeychain
Data
react-native-keychain 10A username and a password per service.
expo-secure-storeA string per key.
keychainA string or bytes per key, plus credentials.
Protection
react-native-keychain 10Seven flags on iOS; on Android biometryAny is the same as currentSet.
expo-secure-storeOne boolean.
keychainSix options, with the same meaning on both platforms.
No biometrics
react-native-keychain 10Android stores the value without protection.
expo-secure-storeAn error.
keychainAn error on both platforms.
Changed biometrics
react-native-keychain 10An error.
expo-secure-storenull, as if nothing were stored.
keychainKEY_INVALIDATED; the item is removed.
Errors
react-native-keychain 10Codes, E_*.
expo-secure-storeNative messages.
keychainCodes on a KeychainError.
Architecture
react-native-keychain 10A spec of Object types, plus the old architecture.
expo-secure-storeExpo Modules.
keychainA typed TurboModule, New Architecture only.

Compared with others goes through every difference.

What it promises​

01

One meaning on both platforms

An option behaves the same on iOS and Android. A platform that can't do it throws, so shared code needs no Platform.select to stay safe.
02

Never weaker, never silent

If the device can't give the protection you asked for, the write fails with a code. A value is never stored with less.
03

Writes never prompt

Storing a value shows no prompt on either platform. Only reading a protected value does.
04

Reads without a lock never wait

A read of an unprotected item doesn't wait for a prompt that is open for another item. hasItem, getItemInfo and getAllKeys never prompt.
05

Errors you can act on

A missing value is null. A cancel, a lockout or a changed fingerprint set is a KeychainError with its own code.
06

Nothing at startup

The module does no work when it loads. A service is read the first time you use it.
The idea in one line

Biometrics is a lock on the value, not a yes from JavaScript.

A check that shows Face ID and returns true lives in your app's code, and on a compromised device the answer can be changed while the token sits unprotected. Here the operating system encrypts the value with a key that works only after the user passes the prompt; without it, the value can't be decrypted at all. How biometrics works explains the details.

Where it fits​

Fits
  • Access and refresh tokens, session secrets, API keys of the user.
  • The key of an encrypted database: MMKV, SQLCipher, op-sqlite.
  • A username and password for a sign-in form, with the password behind Face ID.
  • Bare React Native apps and Expo apps with a dev build, on React Native 0.80 or newer.
  • Apps moving from react-native-keychain or expo-secure-store: the items move with one call, and the compat layer keeps the old calls working.
Not a fit
  • Signing, keys in the Secure Enclave, passkeys: general cryptography is a separate package.
  • Password AutoFill and shared web credentials: that is AuthenticationServices and Credential Manager.
  • Large data: keep files encrypted on disk and their key here.
  • Expo Go, the old architecture and the web (the web has only a mock for tests).

Next​