Skip to main content
@apexnative/keychain

Secure storage for React Native

The iOS Keychain and the Android Keystore behind one API. Every option means the same on both platforms, and protection is never lowered in silence.

Release candidatenpm · 1.0.0-rc.0iOS · Android · Expo
Vault rules

How it is built

01

One meaning on both platforms

An option means the same on iOS and Android. A platform that can’t do it throws, so shared code never needs a Platform.select to stay safe.

02

Never weaker, never silent

If the device can’t give the protection you asked for, the write fails. A value is never stored with less than you asked.

03

Biometrics locks the value

Face ID or a fingerprint unlocks the key that decrypts the item, inside the Secure Enclave or the TEE. There is no boolean to fake.

04

Writes never prompt

Storing a value never shows a prompt on either platform. Only reading a protected item does, and the OS draws it.

05

Errors you can act on

A missing value is null. A changed fingerprint set is KEY_INVALIDATED, not null. Every failure has a code.

06

Nothing at startup

The module does no work when it loads: a service is read the first time you use it, and reads without a prompt never wait for one.

07

New Architecture, bare and Expo

A TurboModule with a typed codegen spec, for React Native 0.80 and newer: bare apps and Expo dev builds alike.

In the vault

What it keeps

Keychain items on iOS, Keystore-encrypted files on Android. Your code sees one key/value store per service.

Turn the key

From a token to a vault behind Face ID

  1. 1Keep a token with keychain.setItem.
  2. 2Make a vault with createKeychain and an accessControl.
  3. 3Read from it: the OS shows the prompt, your code gets the value or an error.
src/session.ts
import { AccessControl, createKeychain, keychain } from '@apexnative/keychain';

await keychain.setItem('accessToken', token);

const vault = createKeychain({
  service: 'vault',
  accessControl: AccessControl.BiometryCurrentSet,
  prompt: { title: 'Unlock your account' },
});

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