expo-secure-store
Version: 55.0.6
Provides a way to encrypt and securely store key-value pairs locally on the device. Uses Keychain on iOS and EncryptedSharedPreferences on Android to ensure data is stored securely.
Installation
npx expo install expo-secure-store
Usage
import * as SecureStore from 'expo-secure-store';
// Save data
await SecureStore.setItemAsync('authToken', 'abc123xyz');
// Read data
const token = await SecureStore.getItemAsync('authToken');
console.log('Token:', token);
// Delete data
await SecureStore.deleteItemAsync('authToken');
API Reference
Methods
setItemAsync(key, value, options)
(key: string, value: string, options?: SecureStoreOptions) => Promise<void>
Stores a key-value pair securelyParameters:
key (string): Storage key
value (string): Value to store
options (SecureStoreOptions): Optional configuration
await SecureStore.setItemAsync('username', 'john_doe');
// With options
await SecureStore.setItemAsync('password', 'secret123', {
keychainAccessible: SecureStore.WHEN_UNLOCKED,
});
getItemAsync(key, options)
(key: string, options?: SecureStoreOptions) => Promise<string | null>
Retrieves a value by keyReturns null if key doesn’t existconst username = await SecureStore.getItemAsync('username');
if (username) {
console.log('Username:', username);
}
deleteItemAsync(key, options)
(key: string, options?: SecureStoreOptions) => Promise<void>
Deletes a stored valueawait SecureStore.deleteItemAsync('authToken');
Checks if SecureStore is available on the platformconst available = await SecureStore.isAvailableAsync();
if (!available) {
console.log('SecureStore not available on this platform');
}
Types
SecureStoreOptions
iOS: Keychain service name. Android: SharedPreferences name{ keychainService: 'myAppKeychain' }
keychainAccessible
KeychainAccessibilityConstant
iOS: When the stored data is accessibleValues:
WHEN_UNLOCKED - Accessible when device is unlocked (default)
AFTER_FIRST_UNLOCK - Accessible after first unlock since boot
ALWAYS - Always accessible (deprecated)
WHEN_PASSCODE_SET_THIS_DEVICE_ONLY - Only when passcode is set
WHEN_UNLOCKED_THIS_DEVICE_ONLY - Device-only, when unlocked
{
keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY
}
iOS: Require Face ID/Touch ID to access{ requireAuthentication: true }
iOS: Prompt shown for biometric authentication{
requireAuthentication: true,
authenticationPrompt: 'Authenticate to access your password'
}
Constants
// Keychain accessibility constants (iOS)
SecureStore.WHEN_UNLOCKED
SecureStore.AFTER_FIRST_UNLOCK
SecureStore.ALWAYS
SecureStore.WHEN_PASSCODE_SET_THIS_DEVICE_ONLY
SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY
SecureStore.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY
SecureStore.ALWAYS_THIS_DEVICE_ONLY
Examples
Basic Storage
import * as SecureStore from 'expo-secure-store';
async function saveCredentials(username: string, password: string) {
try {
await SecureStore.setItemAsync('username', username);
await SecureStore.setItemAsync('password', password);
console.log('Credentials saved');
} catch (error) {
console.error('Error saving credentials:', error);
}
}
async function loadCredentials() {
try {
const username = await SecureStore.getItemAsync('username');
const password = await SecureStore.getItemAsync('password');
return { username, password };
} catch (error) {
console.error('Error loading credentials:', error);
return null;
}
}
Store Auth Token
import * as SecureStore from 'expo-secure-store';
async function login(username: string, password: string) {
// Authenticate with server
const response = await fetch('https://api.example.com/login', {
method: 'POST',
body: JSON.stringify({ username, password }),
});
const { token } = await response.json();
// Store token securely
await SecureStore.setItemAsync('authToken', token);
}
async function getAuthToken() {
return await SecureStore.getItemAsync('authToken');
}
async function logout() {
await SecureStore.deleteItemAsync('authToken');
}
Biometric Protection
import * as SecureStore from 'expo-secure-store';
import * as LocalAuthentication from 'expo-local-authentication';
async function saveSensitiveData(key: string, value: string) {
// Check if biometric auth is available
const hasHardware = await LocalAuthentication.hasHardwareAsync();
const isEnrolled = await LocalAuthentication.isEnrolledAsync();
if (hasHardware && isEnrolled) {
// Store with biometric protection
await SecureStore.setItemAsync(key, value, {
requireAuthentication: true,
authenticationPrompt: 'Authenticate to save data',
});
} else {
// Store without biometric protection
await SecureStore.setItemAsync(key, value);
}
}
async function getSensitiveData(key: string) {
try {
const value = await SecureStore.getItemAsync(key, {
requireAuthentication: true,
authenticationPrompt: 'Authenticate to access data',
});
return value;
} catch (error) {
console.error('Authentication failed:', error);
return null;
}
}
Store JSON Data
import * as SecureStore from 'expo-secure-store';
interface UserSettings {
theme: string;
notifications: boolean;
language: string;
}
async function saveSettings(settings: UserSettings) {
const jsonString = JSON.stringify(settings);
await SecureStore.setItemAsync('userSettings', jsonString);
}
async function loadSettings(): Promise<UserSettings | null> {
const jsonString = await SecureStore.getItemAsync('userSettings');
if (!jsonString) return null;
try {
return JSON.parse(jsonString);
} catch (error) {
console.error('Error parsing settings:', error);
return null;
}
}
Custom Keychain Service
import * as SecureStore from 'expo-secure-store';
const KEYCHAIN_SERVICE = 'com.myapp.secure';
async function saveToCustomKeychain(key: string, value: string) {
await SecureStore.setItemAsync(key, value, {
keychainService: KEYCHAIN_SERVICE,
});
}
async function getFromCustomKeychain(key: string) {
return await SecureStore.getItemAsync(key, {
keychainService: KEYCHAIN_SERVICE,
});
}
Complete Auth Example
import * as SecureStore from 'expo-secure-store';
import { useState, useEffect } from 'react';
import { View, TextInput, Button, Text, StyleSheet } from 'react-native';
export default function AuthExample() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [isLoggedIn, setIsLoggedIn] = useState(false);
useEffect(() => {
checkLoginStatus();
}, []);
async function checkLoginStatus() {
const token = await SecureStore.getItemAsync('authToken');
setIsLoggedIn(!!token);
}
async function handleLogin() {
// Simulate API call
const token = 'mock-jwt-token-' + Date.now();
// Store credentials
await SecureStore.setItemAsync('authToken', token);
await SecureStore.setItemAsync('username', username);
setIsLoggedIn(true);
}
async function handleLogout() {
await SecureStore.deleteItemAsync('authToken');
await SecureStore.deleteItemAsync('username');
setIsLoggedIn(false);
setUsername('');
setPassword('');
}
if (isLoggedIn) {
return (
<View style={styles.container}>
<Text style={styles.title}>Welcome back!</Text>
<Button title="Logout" onPress={handleLogout} />
</View>
);
}
return (
<View style={styles.container}>
<Text style={styles.title}>Login</Text>
<TextInput
style={styles.input}
placeholder="Username"
value={username}
onChangeText={setUsername}
autoCapitalize="none"
/>
<TextInput
style={styles.input}
placeholder="Password"
value={password}
onChangeText={setPassword}
secureTextEntry
/>
<Button title="Login" onPress={handleLogin} />
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', padding: 20, gap: 15 },
title: { fontSize: 24, fontWeight: 'bold', textAlign: 'center' },
input: {
borderWidth: 1,
borderColor: '#ccc',
padding: 10,
borderRadius: 5,
},
});
| Platform | Supported |
|---|
| iOS | ✅ |
| Android | ✅ |
| Web | ❌ |
On iOS, SecureStore uses the system Keychain. On Android, it uses EncryptedSharedPreferences with AES-256 encryption.
Security Features
iOS (Keychain)
- Hardware-backed encryption when available
- Secure Enclave support
- Face ID/Touch ID integration
- Automatic iCloud Keychain sync (optional)
- Data persists across app reinstalls (configurable)
Android (EncryptedSharedPreferences)
- AES-256 encryption
- Hardware-backed encryption on supported devices
- Data removed on app uninstall
Best Practices
- Store Only Sensitive Data: Don’t use SecureStore for non-sensitive data (use AsyncStorage instead)
- Keep Values Small: Limit stored values to a few kilobytes
- Handle Errors: Always wrap operations in try/catch blocks
- Biometric Auth: Use
requireAuthentication for highly sensitive data
- Clean Up: Delete sensitive data when no longer needed
- Check Availability: Use
isAvailableAsync() before operations
SecureStore is not available on web. For cross-platform apps, implement a fallback storage mechanism or handle gracefully.
Common Use Cases
- Auth tokens: Store JWT tokens or session IDs
- API keys: Store API keys and secrets
- User credentials: Store encrypted passwords (when necessary)
- Payment info: Store sensitive payment tokens
- Private keys: Store cryptographic keys
Limitations
- Value size: Limited to a few kilobytes per entry
- Not for large data: Use file system for large files
- Web not supported: No secure storage equivalent in browsers
- No synchronization: Data not automatically synced across devices
Resources