A user deletes and reinstalls an Expo app and is still signed in on iOS but not on Android; how does expo-secure-store explain that?
answer
- the Keychain outlives the app
- same bundle ID brings it back
- Keystore keys die with the app
- Auto Backup excludes SecureStore prefs
- first-launch marker clears stale items
basics
~20 sOn iOS, expo-secure-store values live in the Keychain, which can survive uninstalling when the app is reinstalled with the same bundle ID. On Android the Keystore keys are deleted with the app, so values are gone, and Auto Backup is configured to exclude them.
solid answer
~50 siOS Keychain items are not stored in the app's sandbox, so uninstalling does not reliably remove them; the expo-secure-store docs say values persist across reinstall with the same bundle ID, while warning not to rely on it. The old session token is found at the next launch, and the user appears signed in. On Android, values are encrypted with Keystore keys that are deleted on uninstall, so nothing is readable afterwards. Android Auto Backup could restore the `SecureStore` SharedPreferences file, but its ciphertext would be undecryptable, so the config plugin's `configureAndroidBackup` (default `true`) adds rules excluding it; if a stale entry is restored anyway, the module deletes it on read and returns `null`. For consistent behaviour, keep a first-launch marker in ordinary app storage, which uninstall wipes on both platforms; if it is missing, delete your SecureStore keys before reading them.
code
typescript · 12 linesimport * as SecureStore from 'expo-secure-store';
import Storage from 'expo-sqlite/kv-store';
const SECURE_KEYS = ['session_token', 'refresh_token'];
const INSTALL_MARKER = 'installed_v1'; // lives in the app sandbox, wiped by uninstall
export async function clearSecretsOnFreshInstall(): Promise<void> {
if (Storage.getItemSync(INSTALL_MARKER) === 'true') return;
// Fresh install: on iOS the Keychain may still hold items from a previous install.
await Promise.all(SECURE_KEYS.map((key) => SecureStore.deleteItemAsync(key)));
Storage.setItemSync(INSTALL_MARKER, 'true');
}go deeper
Recall that SecureStore values can survive reinstall on iOS but never on Android, and that ordinary app storage is wiped on both.
Explain why: Keychain items live outside the sandbox, Android Keystore keys are deleted with the app, and Auto Backup is set to skip SecureStore.
Make behaviour deliberate with a first-run marker that clears stale items, keep backup rules correct when customising them, and never treat SecureStore as permanent.
Set the product rule for sessions across reinstall and device migration, then enforce it the same way on both platforms.
## The observation A crypto-price tracker's user deletes the app and installs it again. On an iPhone they land on their watchlist, still signed in. On an Android phone they see the sign-in screen. Both are expected behaviours of the storage expo-secure-store sits on, and interviewers use this difference to see whether a candidate knows where credentials really live. ## iOS: the Keychain is not inside the app On iOS, expo-secure-store saves each value as a Keychain item. The Keychain is a system store, separate from the app's sandbox directory. Deleting an app removes its sandbox (documents, caches, SQLite files, preferences), but Keychain items can remain. The expo-secure-store docs state it plainly: - data **will persist across app uninstallations** when the app is reinstalled with the **same bundle ID**; - this is **not guaranteed**, and you should never rely on it. So after a reinstall, `getItemAsync('session_token')` can return the old token, while everything else the app stored is gone. ## Android: the key dies with the app On Android, expo-secure-store encrypts each value with an AES key in the Android Keystore and writes the ciphertext to a private SharedPreferences file named `SecureStore`. The Keystore entries belong to the app and are deleted when it is uninstalled, and the SharedPreferences file lives in the app's data, which is removed too. The docs: data **will not be preserved** upon uninstall. ## Android Auto Backup Android's Auto Backup (Android 6.0, API 23, and later) can back up an app's files and SharedPreferences and restore them on reinstall or on a new device. For SecureStore that would be harmful: the restored `SecureStore` file contains ciphertext whose key no longer exists. The library handles this at two levels: 1. **Build time.** The `expo-secure-store` config plugin option `configureAndroidBackup` defaults to `true`. It points the manifest's `android:fullBackupContent` (Android 11 and lower) and `android:dataExtractionRules` (Android 12 and higher) at rule files that include all SharedPreferences except `SecureStore`. If the app already has its own backup rules, the plugin warns instead; you then add `<exclude domain="sharedpref" path="SecureStore"/>` yourself and set `configureAndroidBackup` to `false`. 2. **Run time.** If an undecryptable entry does appear, the Android module catches the decryption failure, deletes the entry, and `getItemAsync` resolves `null`. ## Making both platforms behave the same Most apps want "reinstall means signed out" everywhere. The common pattern uses the fact that ordinary app storage **is** wiped by uninstall on both platforms: 1. At launch, before reading any secrets, check for a first-run marker in ordinary storage (for example a kv-store key or a file). 2. If the marker is missing, this is a fresh install: call `deleteItemAsync` for each SecureStore key the app uses, then write the marker. 3. Only then read the session token. On Android this is a no-op; on iOS it removes the stale Keychain items left by the previous install. ## Reinstall is not the same as a new device Two different events are easy to conflate: - **Reinstall on the same device.** On iOS, Keychain items may still be there; on Android, the Keystore keys are gone. - **Restore onto a new device.** On iOS, whether an item travels with the backup is decided by `keychainAccessible`: the `_THIS_DEVICE_ONLY` values stay behind. On Android, the plugin's data-extraction rules exclude the `SecureStore` file from both cloud backup and device-to-device transfer, and the Keystore keys never move. A session policy needs an answer for both: whether a reinstall keeps the session, and whether a new phone does. ## Related choices Whatever the policy, the server remains the authority: a token that survives a reinstall can still be revoked there, and a sign-out should also invalidate the token server-side, not only delete it from the device. | Goal | Tool | |---|---| | Token must not move to a new iPhone via backup | `keychainAccessible` with a `_THIS_DEVICE_ONLY` value | | Token must not survive reinstall on iOS | first-run marker plus `deleteItemAsync` | | Custom Android backup rules | exclude `sharedpref` `SecureStore`, set `configureAndroidBackup: false` | | Irreplaceable data | not SecureStore; it is not a single source of truth |
- Why not simply trust the iOS behaviour and keep users signed in across reinstalls?The docs say it is not guaranteed and should not be relied on, and it also brings back a token the user may have expected to disappear, for example on a device being handed over. Behaviour then differs by platform. Deciding explicitly with a first-run marker is predictable.
- What happens if Auto Backup restores the SecureStore file on Android anyway?The restored entries were encrypted with a Keystore key that no longer exists. When the app reads one, decryption fails, the module deletes the out-of-sync entry and `getItemAsync` resolves `null`, so the app simply appears signed out.
saying these in an interview costs you the question
- Uninstalling an iOS app always deletes its SecureStore values
- Android SecureStore values survive reinstall through Auto Backup
- The iOS reinstall persistence is guaranteed and safe to build on
- Restored SecureStore preferences on Android decrypt fine with the new key
- configureAndroidBackup must be enabled by hand to exclude SecureStore