In expo-secure-store, what does requireAuthentication: true change, and how does it behave differently on iOS and Android?
answer
- biometrics guard the item
- iOS: biometryCurrentSet
- iOS prompts on read and update, not create
- Android: every operation authenticates
- Face ID needs a usage description
basics
~20 srequireAuthentication: true ties the stored value to the user's biometrics, so the system prompts before releasing it. On iOS the prompt appears when reading or updating an existing value; on Android every operation requires authentication.
solid answer
~40 sWith `requireAuthentication: true`, iOS saves the item with access control `biometryCurrentSet`, and Android creates a Keystore key with user authentication required. Platforms differ: iOS prompts only when reading or updating an existing value, not when creating it; Android requires authentication for every operation. `authenticationPrompt` sets the message shown. On iOS the app must declare `NSFaceIDUsageDescription`, set through the config plugin's `faceIDPermission`; without it the save throws, and Expo Go lacks that key on Face ID devices, so the option needs a development build. Check `canUseBiometricAuthentication()` first, test on a real device because simulators skip the prompt, and store protected items under their own `keychainService`. Values saved this way become unreadable when biometric enrolment changes, and synchronous calls freeze the UI until the user answers the prompt.
code
typescript · 20 linesimport * as SecureStore from 'expo-secure-store';
const PROTECTED: SecureStore.SecureStoreOptions = {
keychainService: 'portfolio-auth',
requireAuthentication: true,
authenticationPrompt: 'Unlock your portfolio',
};
export async function savePortfolioKey(value: string): Promise<boolean> {
if (!SecureStore.canUseBiometricAuthentication()) {
return false; // fall back to the app's own PIN flow
}
await SecureStore.setItemAsync('portfolio_key', value, PROTECTED);
return true;
}
export function readPortfolioKey(): Promise<string | null> {
// Prompts for biometrics; resolves null if biometrics changed since it was saved.
return SecureStore.getItemAsync('portfolio_key', PROTECTED);
}go deeper
Recall that requireAuthentication: true makes the OS ask for biometrics before releasing the value, and that it needs a real device to test.
Explain the platform split, iOS prompts on read and update while Android authenticates every operation, plus the Face ID usage description and capability check.
Design around it: a separate keychainService, async calls, cancelled prompts, invalidation on re-enrolment, and keeping background secrets unprotected.
Decide which secrets deserve a biometric gate and what the fallback is on devices without biometrics or after invalidation.
## What the option adds By default, a SecureStore value is readable by the app whenever the platform's storage allows it. `requireAuthentication: true` adds a second gate: the operating system asks the user to authenticate with biometrics before it releases the secret. In a crypto-price tracker this fits a key that unlocks trading or portfolio details, where the app wants "Face ID to open" rather than just a stored session. The option is part of `SecureStoreOptions` and defaults to `false`. A companion option, `authenticationPrompt`, sets the message displayed in the system prompt. ## How each platform implements it **iOS.** The item is saved with an access-control object created with the `biometryCurrentSet` flag, combined with the item's `keychainAccessible` level. That flag means the item is bound to the **currently enrolled** biometrics. The documented behaviour: - the user is prompted when **reading** or **updating** an existing value; - **creating** a new value does not prompt. **Android.** The value is encrypted with a Keystore key generated with user authentication required (`setUserAuthenticationRequired(true)`), which needs API 23 or later. The documented behaviour: - user authentication is required for **all** operations, including writing. | | iOS | Android | |---|---|---| | Mechanism | Keychain access control, `biometryCurrentSet` | Keystore key requiring user authentication | | Prompt on create | no | yes | | Prompt on read / update | yes | yes | | Invalidated when biometrics change | yes | yes | ## Setup the app needs 1. **The Face ID usage description on iOS.** The iOS implementation refuses to save an authenticated item when `NSFaceIDUsageDescription` is missing from the app's Info.plist. The `expo-secure-store` config plugin sets it through its `faceIDPermission` property (with a default message), so a development or release build made with prebuild has it. 2. **Not in Expo Go on Face ID devices.** The docs state the option is unsupported in Expo Go when biometric authentication is available, because Expo Go lacks that usage description. Test it in a development build. 3. **A capability check.** `canUseBiometricAuthentication()` returns `true` only when the device supports biometrics and the enrolled method is secure enough; always `false` on tvOS. Offer the feature only when it returns `true`. 4. **A real device.** Simulators and emulators do not require biometric authentication when retrieving secrets, so the prompt, and its failure paths, can only be tested on hardware. ## A typical flow in the app 1. After a normal sign-in, check `canUseBiometricAuthentication()`. 2. If it returns `true`, ask the user whether to enable biometric unlock; do not turn it on silently. 3. On opt-in, save the secret with `requireAuthentication: true` under a dedicated `keychainService`, plus an `authenticationPrompt` explaining why. 4. On the next launch, read it with the same options; the system shows the prompt. 5. Handle three outcomes: a value (unlocked), a rejection (cancelled or failed, offer retry or password), and `null` (invalidated, sign in again). ## Design rules that follow - **Separate `keychainService`.** The option's docs note that full functionality needs a freshly generated key and does not work together with the `keychainService` used for non-authenticated operations. Keep authenticated entries under their own service name, and pass the same name when reading them. - **Async only in UI paths.** The synchronous `getItem` and `setItem` block the JavaScript thread until the user authenticates, so the app freezes while the prompt is up. - **Expect the user to say no.** A cancelled or failed prompt rejects the call; show a retry, not a sign-out. - **Expect invalidation.** Enrolling a new fingerprint or changing the face profile invalidates these entries; a later read resolves `null`, and the app must re-authenticate the user another way and save a new value. - **Do not guard everything.** A session token that background work needs cannot sit behind a biometric prompt; protect only what the user should consciously unlock. ## What the option is not It is not a login system. The prompt proves that someone with an enrolled biometric is holding this device right now; it says nothing to your server. The server still authenticates the token the app sends. Nor is it a replacement for the device passcode: it adds a gate in front of one secret, and the rest of the app's data stays as protected as the device itself.
- Why can't you verify requireAuthentication behaviour on a simulator?The expo-secure-store docs note that emulators and simulators do not require biometric authentication when retrieving secrets, unlike real devices. The prompt, a cancelled prompt and invalidation after re-enrolment only happen on hardware, so test those paths on a phone.
- Why store authenticated items under a separate keychainService?The option works fully only with a freshly generated key and not with the service used for non-authenticated items. On Android, handling an invalidated key also removes the authenticated entries under that service, so a dedicated service keeps unprotected values out of that blast radius.
saying these in an interview costs you the question
- requireAuthentication prompts on iOS every time a new value is created
- Android only prompts on reads, like iOS
- requireAuthentication works in Expo Go on Face ID devices without setup
- A simulator is enough to test the biometric prompt
- Synchronous getItem is fine for authenticated items because it is quick