In a React Native app, where should a refresh token be stored, and why is AsyncStorage the wrong place for it?
answer
- sandboxed is not the same as encrypted
- AsyncStorage: unencrypted key-value store
- core React Native ships no secure store
- iOS Keychain item vs Android Keystore key
- expo-secure-store or react-native-keychain
basics
~20 sA refresh token belongs in platform secure storage: the iOS Keychain, or a value encrypted with an Android Keystore key, reached through expo-secure-store or react-native-keychain. AsyncStorage is an unencrypted store that anyone reading the app's files can open.
solid answer
~30 sReact Native core has no secure storage, and the community `AsyncStorage` module is, in its own words, an asynchronous, **unencrypted**, persistent key-value store. The app sandbox keeps other ordinary apps out, but not a rooted or jailbroken device, a copied backup, or a debuggable build, so a long-lived refresh token there is effectively plaintext on disk. The token belongs in the **iOS Keychain** or behind an **Android Keystore** key, which you reach through a wrapper such as `expo-secure-store` or `react-native-keychain`. The short-lived access token can stay in memory, and AsyncStorage keeps only non-sensitive data such as preferences.
code
typescript · 12 linesimport * as SecureStore from 'expo-secure-store';
let accessToken: string | null = null; // memory only
export async function saveSession(refreshToken: string, nextAccessToken: string): Promise<void> {
await SecureStore.setItemAsync('refreshToken', refreshToken);
accessToken = nextAccessToken;
}
export function currentAccessToken(): string | null {
return accessToken;
}go deeper
Remember the one-liner: AsyncStorage is unencrypted, tokens go to the Keychain or Keystore through a library such as expo-secure-store or react-native-keychain.
Explain why the sandbox is not protection, and split data by sensitivity: refresh token in secure storage, access token in memory, preferences in AsyncStorage.
Show you audit the whole path: persisted state blobs, logs and crash reports that might carry the token, and what secure storage does not cover once JavaScript holds the value.
Frame storage as one layer among several: token lifetime, server-side revocation and device risk decide how much the on-device choice actually buys.
## Why interviewers ask it "Where does the refresh token live?" is one of the most common React Native security screening questions because the honest answer starts with a fact many candidates miss: **React Native core does not ship any way to store sensitive data**. The official React Native security guide says so, and then points at the platform facilities and at libraries that wrap them. A candidate who answers "AsyncStorage, it's local to the app" has confused *isolation* with *protection*. A **refresh token** is the long-lived credential an app trades for new short-lived access tokens. Whoever holds it can keep minting access tokens until the server revokes it, so in a brokerage app it is effectively the keys to the trading account. ## What AsyncStorage actually is `@react-native-async-storage/async-storage` is a community module, not part of core. Its README and the React Native security guide both describe it as an **asynchronous, unencrypted, persistent key-value store**. What that means in practice: - **It is sandboxed.** Each app gets its own storage, so another ordinary app on the phone cannot read it. - **It is not encrypted.** Values are written as plain data into files inside that sandbox (AsyncStorage 3's storage instances use a SQLite database on iOS and Android). - **It has no user-presence check.** Any code running in the app, including a compromised dependency, can read every key. The sandbox stops other apps, but a token's realistic threats are different: 1. A **rooted or jailbroken device**, where tooling can read any app's files. 2. **Copies of app data** made by device backups or transfer tools, unless the app excludes those files. 3. A **debuggable build** or an unlocked phone plugged into a laptop. 4. **Your own code**: the React Native guide specifically warns about persisting a whole state tree, token included, into AsyncStorage. The React Native guide's own table puts it bluntly: use AsyncStorage for non-sensitive data and persisted app state; do not use it for token storage or secrets. ## Where the token goes instead The two platforms provide different primitives, and a secure-storage library hides the difference behind one key-value API. | Platform | OS facility | What it protects | Reached from React Native through | |---|---|---|---| | iOS | **Keychain Services** | the secret itself, stored as a keychain item the OS encrypts | `expo-secure-store`, `react-native-keychain` | | Android | **Android Keystore** | a non-exportable key; the token is encrypted with it and the ciphertext kept in app storage | `expo-secure-store`, `react-native-keychain` | In an Expo project `expo-secure-store` is the usual choice, and it is available in Expo Go; `react-native-keychain` is a common pick in bare projects. Whichever you use, the JavaScript API is a small asynchronous key-value interface, so moving a token out of AsyncStorage is mostly a matter of swapping calls and migrating existing values once. ## Splitting a brokerage app's data correctly 1. **Refresh token**: secure storage, optionally protected by a biometric access-control flag so the OS releases it only after Face ID or a fingerprint. 2. **Access token**: memory only. It is short-lived, and losing it when the process dies is the point; the app gets a fresh one with the refresh token. 3. **Non-sensitive state**: the chosen theme, whether onboarding was completed, the last-opened watchlist tab. AsyncStorage is fine here. 4. **Anything you would not print in a log**: keep it out of AsyncStorage, including inside a persisted state blob. ## What secure storage does not do - It protects the token **at rest**. Once JavaScript reads it into memory, it is an ordinary string in the JS heap. - It does not make a **fully compromised device** safe; it raises the cost of extraction, it does not remove it. - It is not a place to hide **app-wide secrets** such as a third-party API key shipped with the app: that key was already in the bundle before you stored it anywhere. ## Common wrong answers - "It's fine because each app is sandboxed" confuses isolation with encryption. - "I base64-encode it first" adds no protection: base64 is an encoding with no key. - "I encrypt it myself and put the ciphertext in AsyncStorage" only moves the problem to where the encryption key lives, and the right answer to that is again the Keychain or Keystore.
- Why not encrypt the token yourself and keep the ciphertext in AsyncStorage?Because the question becomes where the encryption key lives. A key in the JavaScript bundle can be extracted along with the ciphertext. A key held in the Keychain or Android Keystore works, but then you have rebuilt what `expo-secure-store` or `react-native-keychain` already do, with more room for mistakes.
- Is there anything in a brokerage app that AsyncStorage is right for?Yes: non-sensitive, rebuildable state such as the theme, an onboarding-completed flag or the last-selected tab. The rule is about sensitivity, not about AsyncStorage being bad; it is the wrong tool for credentials and the right one for small preferences.
- Why keep the access token only in memory?It is short-lived, so persisting it buys little and adds another copy to protect. When the process dies the app simply uses the refresh token from secure storage to obtain a new access token.
AsyncStorage is a locked hotel room with your passport lying on the desk: other guests cannot walk in, but anyone who gets into the room reads it. The Keychain or Keystore is the room safe, which opens only for its own code.
saying these in an interview costs you the question
- AsyncStorage is safe for tokens because each app is sandboxed.
- AsyncStorage encrypts values on disk by default.
- React Native core has a built-in secure storage API.
- Base64-encoding a token before storing it hides it.
- Persisting the whole state tree is fine even if it holds the token.