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.
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.
null, as if nothing were stored.KEY_INVALIDATED; the item is removed.E_*.KeychainError.Compared with others goes through every difference.
What it promises
One meaning on both platforms
Platform.select to stay safe.Never weaker, never silent
Writes never prompt
Reads without a lock never wait
hasItem, getItemInfo and getAllKeys never prompt.Errors you can act on
null. A cancel, a lockout or a changed fingerprint set is a KeychainError with its own code.Nothing at startup
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
- 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.
- 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
- Installation: the package in a bare app or an Expo app.
- Quick start: a token, a vault behind Face ID, and the errors to handle.
- Protection: every value of
accessibleandaccessControl. - From react-native-keychain or from expo-secure-store: moving an app and its users' data.