Storing Tokens Safely in React Native: Keychain, Keystore, SecureStore
Store auth tokens safely in React Native: Keychain and Keystore via expo-secure-store or react-native-keychain, token strategy, biometrics and pitfalls.
Full-Stack, Mobile & AI Engineer
Most React Native security problems aren't sophisticated attacks. They're tokens saved in plain AsyncStorage, API keys bundled into the JavaScript, and sessions that never expire. Each is easy to avoid once you know where secrets belong on a phone.
This guide covers React Native secure storage in practice: what the iOS Keychain and Android Keystore protect, how to use them through expo-secure-store or react-native-keychain, a sensible token strategy, biometric protection, and the pitfalls that catch even experienced teams.
Key takeaways
- Store tokens and secrets in the iOS Keychain and Android Keystore, via
expo-secure-storeorreact-native-keychain, never in plain AsyncStorage or unencrypted MMKV. - Never put real secrets in the app bundle. Anything in your JavaScript (including
EXPO_PUBLIC_variables) can be extracted. - Use short-lived access tokens and a refresh token, rotated on use and revocable on the server.
- Protect sensitive actions with biometrics, not just sign-in.
- On iOS, Keychain items can survive an app reinstall, so clear stale credentials on first launch after installation.
What the platform secure stores do
- iOS Keychain stores small secrets encrypted by the system, tied to your app (and optionally to the device passcode or to biometrics).
- Android Keystore keeps cryptographic keys in hardware-backed storage where available; libraries use those keys to encrypt values stored on the device.
Both are designed for small secrets: tokens, passwords, encryption keys. They're not general-purpose databases, and treating them like one leads to slow reads and size errors.
Option 1: expo-secure-store
expo-secure-store wraps the Keychain and Keystore with a simple API and works in Expo and bare apps:
import * as SecureStore from "expo-secure-store";
await SecureStore.setItemAsync("refreshToken", token, {
keychainAccessible: SecureStore.AFTER_FIRST_UNLOCK,
});
const refreshToken = await SecureStore.getItemAsync("refreshToken");
await SecureStore.deleteItemAsync("refreshToken");Keep values small (store a token, not a whole user object). The keychainAccessible option controls when the item can be read; the AFTER_FIRST_UNLOCK setting lets background tasks refresh tokens, while still protecting the data until the user has entered their passcode once after the phone restarts. For values that should require biometrics on every read, the requireAuthentication option prompts Face ID or fingerprint.
Option 2: react-native-keychain
The React Native Keychain library (react-native-keychain) offers more control over access policies (for example requiring biometrics or the device passcode, and choosing security levels on Android). It suits apps with stricter requirements, such as banking or regulated data. Both libraries ultimately rely on the same platform stores.
A sensible token strategy
| Token | Lifetime | Where it lives |
|---|---|---|
| Access token | Minutes (for example 15) | In memory; optionally Secure Store for fast resume |
| Refresh token | Days to weeks | Secure Store / Keychain |
| Session on the server | Revocable | Server database |
- Rotate refresh tokens on every use, and invalidate the old one, so a stolen refresh token stops working quickly.
- Revoke on sign-out and password change, on the server, not only on the device.
- Handle refresh in one place (an API client interceptor), so concurrent requests don't trigger several refreshes at once.
- Bind sensitive sessions to the device where your auth provider supports it.
Secrets that must never ship in the app
Anything included in the app bundle can be extracted by someone with the app file and some patience. That includes JavaScript constants, EXPO_PUBLIC_ environment variables and config files. So:
- Never ship server secrets: payment secret keys, database credentials, admin API keys, third-party secrets.
- Proxy third-party APIs through your backend when they require a secret key.
- Treat public keys as public: Stripe publishable keys, Firebase config and similar are designed to be visible, and must be protected by server-side rules instead.
Encrypting larger local data
When you need more than a token protected (cached personal records, offline documents):
- Encrypt MMKV with an encryption key, or use an encrypted SQLite build.
- Generate that key once and store it in Secure Store, never hard-code it.
- Exclude sensitive local files from device backups where your platform setup allows.
From the field: Expetitle, the real estate closing platform I built, handled personal information and documents for buyers, sellers, realtors and brokers to escrow-grade standards: role-based access, audit trails, encrypted storage and careful PII handling. On apps like that, where tokens and keys live on the device is a design decision made on day one, not a fix after a security review.
Biometrics for sensitive actions
Biometric prompts (Face ID, Touch ID, fingerprint) are most valuable at moments of risk, not only at sign-in: viewing documents, confirming payments, changing account details. With expo-local-authentication you can ask the user to authenticate before revealing or using a secret, and with Secure Store's requireAuthentication the secret itself is protected by biometrics.
Always offer a fallback (device passcode or re-entering the password), and don't treat biometrics as a replacement for server-side authorisation.
Pitfalls to avoid
- Keychain survives reinstall on iOS. A user who deletes and reinstalls the app may find old credentials still present. Store a "has launched" flag in regular storage, and clear secure items on the first launch after installation.
- Logging tokens. Remove tokens from logs, crash reports and analytics events.
- Storing whole user objects in Secure Store. Keep it to small secrets; store the rest normally (encrypted if sensitive).
- Trusting the client. Every permission check must also happen on the server.
- No session expiry. Long-lived tokens with no rotation turn one leak into permanent access.
FAQ
Where should I store JWTs in React Native?
Store refresh tokens (and access tokens if you persist them) in secure storage backed by the iOS Keychain and Android Keystore, using expo-secure-store or react-native-keychain. Keep access tokens short-lived, ideally in memory, and never store tokens in plain AsyncStorage.
Is AsyncStorage secure for tokens?
No. AsyncStorage stores data unencrypted, so tokens there can be read from backups or compromised devices. Use the platform secure stores for secrets, and reserve AsyncStorage or MMKV for non-sensitive data.
Can I hide API keys in a React Native app?
No. Anything shipped inside the app can be extracted. Keep secret keys on your server and call third-party APIs through your backend. Keys designed to be public, like a Stripe publishable key, are fine in the app as long as server-side rules protect your data.
Why do credentials remain after reinstalling my iOS app?
iOS Keychain items can persist after an app is deleted and reinstalled. Store a simple "first launch" flag in regular storage (which is removed on uninstall), and when it's missing, clear any secure items left over from the previous install.
Next steps
If you're unsure whether your app stores tokens and secrets safely, I can review the auth flow, storage and API calls and fix the gaps before they become an incident. Get in touch, or see my services.
Sources
- SecureStore, Expo
- react-native-keychain, GitHub
- LocalAuthentication, Expo
- Security (React Native), React Native docs
- OWASP Mobile Application Security, OWASP
Related
React Native Security Checklist, Based on OWASP MASVS
React Native security best practices mapped to OWASP MASVS: storage, secrets, auth, network, deep links, WebViews, dependencies, resilience and privacy.
Detox vs Maestro: End-to-End Testing React Native in 2026
Detox vs Maestro for React Native E2E tests in 2026: how they work, the same test in both, flakiness, Expo and CI support, and which to choose.
React Native Errors You'll Hit in 2026, and How to Fix Each
Common React Native errors and fixes: Metro, native modules, rendering, hooks, Hermes, iOS and Android builds, library upgrades, and how to debug the rest.
