skip to content

In expo-secure-store, what does requireAuthentication: true change, and how does it behave differently on iOS and Android?

level: middleimportance: should knowfreq 30%

answer

  1. biometrics guard the item
  2. iOS: biometryCurrentSet
  3. iOS prompts on read and update, not create
  4. Android: every operation authenticates
  5. Face ID needs a usage description

basics

~20 s

requireAuthentication: 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 s

With `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 lines
typescript
import * 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

for a junior

Recall that requireAuthentication: true makes the OS ask for biometrics before releasing the value, and that it needs a real device to test.

for a middle

Explain the platform split, iOS prompts on read and update while Android authenticates every operation, plus the Face ID usage description and capability check.

for a senior

Design around it: a separate keychainService, async calls, cancelled prompts, invalidation on re-enrolment, and keeping background secrets unprotected.

for a principal

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