This guide assumes you already have a basic Expo project. To create one, see the guides for Windows and Mac.
You can also use SecureStore in a React Native CLI project with some additional setup.
Expo SecureStore encrypts key-value pairs and stores them locally on the device. Each Expo project has its own storage and cannot access another project's data. (Expo documentation)
React Native already has AsyncStorage for storing key-value data on the device. Why use SecureStore as well? As its name suggests, SecureStore offers stronger protection for sensitive values.
On Android, SecureStore stores values in SharedPreferences encrypted with the Android Keystore system. On iOS, it uses Keychain Services and stores values as kSecClassGenericPassword items. iOS also lets you set kSecAttrAccessible to control when a value can be retrieved.
The source describes SecureStore data as being deleted when the app is uninstalled. On iOS, however, Keychain data may persist after reinstalling, so do not rely on uninstalling to clear it. If the user changes biometric settings, such as by adding a fingerprint, data protected with requireAuthentication set to true can become inaccessible.
For a React Native CLI project, run the following command.
npx pod-install
Set Encryption Export Compliance Information for App Review ๐
App Store Connect asks which encryption algorithms your app implements when you publish it or submit a build to TestFlight. This is part of the export compliance process.
During Apple review, you may see a question like the one below about what type of encryption the app uses.
If expo-secure-store is your only relevant use of encryption, you can set ios.config.usesNonExemptEncryption to false to declare that the app does not use non-exempt encryption.
import * as SecureStore from 'expo-secure-store';import { useState } from 'react';import { Text, View, StyleSheet, TextInput, Button, Alert } from 'react-native';async function save(key: string, value: string) { await SecureStore.setItemAsync(key, value);}async function getValueFor(key: string) { let result = await SecureStore.getItemAsync(key); if (result) { Alert.alert("๐ Here's your value ๐ \n" + result); } else { Alert.alert('No values stored under that key.'); }}const App = () => { const [key, onChangeKey] = useState('Your key here'); const [value, onChangeValue] = useState('Your value here'); return ( <View style={styles.container}> <Text style={styles.paragraph}>Save an item, and grab it later!</Text> {} <TextInput style={styles.textInput} clearTextOnFocus onChangeText={(text) => onChangeKey(text)} value={key} /> <TextInput style={styles.textInput} clearTextOnFocus onChangeText={(text) => onChangeValue(text)} value={value} /> {} <Button title="Save this key/value pair" onPress={() => { save(key, value); onChangeKey('Your key here'); onChangeValue('Your value here'); }} /> <Text style={styles.paragraph}>๐ Enter your key ๐</Text> <TextInput style={styles.textInput} onSubmitEditing={(event) => { getValueFor(event.nativeEvent.text); }} placeholder="Enter the key for the value you want to get" /> </View> );};export default App;const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', paddingTop: 10, backgroundColor: '#ecf0f1', padding: 8, }, paragraph: { marginTop: 34, margin: 24, fontSize: 18, fontWeight: 'bold', textAlign: 'center', }, textInput: { height: 35, borderColor: 'gray', borderWidth: 0.5, padding: 4, },});
The example saves a value with SecureStore.setItemAsync(key, value) and reads it with SecureStore.getItemAsync(key). The API is similar to AsyncStorage, with the benefit of encrypted storage.
Use SecureStore.deleteItemAsync(key, options) to delete a value by key. The original article also mentions SecureStore.deleteAllItemsAsync(), but that method is not part of the documented Expo SecureStore API. To clear values, delete each key you manage.
SecureStore supports biometric authentication. Set requireAuthentication to require it and authenticationPrompt to supply a message for the prompt.
Use SecureStore.canUseBiometricAuthentication() to check whether biometric authentication is available on the device.
/app/index.tsx
// ...async function save(key: string, value: string) { if (SecureStore.canUseBiometricAuthenticationAsync()) { await SecureStore.setItemAsync(key, value, { requireAuthentication: true, authenticationPrompt: 'Authenticate to save the value', }); } else { await SecureStore.setItemAsync(key, value); }}// ...
The platforms differ. On Android, each operation requires authentication. On iOS, the user is prompted only when reading or updating an existing value. Creating a new value does not show the prompt.
Test on a physical device for the biometric behavior in this example.
Choose SecureStore or AsyncStorage based on the data you're storing. SecureStore is a good fit for login tokens and sensitive user information. Use AsyncStorage for ordinary preferences such as the user's theme.