Skip to main content

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 exist
const username = await SecureStore.getItemAsync('username');
if (username) {
  console.log('Username:', username);
}
deleteItemAsync(key, options)
(key: string, options?: SecureStoreOptions) => Promise<void>
Deletes a stored value
await SecureStore.deleteItemAsync('authToken');
isAvailableAsync()
() => Promise<boolean>
Checks if SecureStore is available on the platform
const available = await SecureStore.isAvailableAsync();
if (!available) {
  console.log('SecureStore not available on this platform');
}

Types

SecureStoreOptions

keychainService
string
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
}
requireAuthentication
boolean
iOS: Require Face ID/Touch ID to access
{ requireAuthentication: true }
authenticationPrompt
string
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 Support

PlatformSupported
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

  1. Store Only Sensitive Data: Don’t use SecureStore for non-sensitive data (use AsyncStorage instead)
  2. Keep Values Small: Limit stored values to a few kilobytes
  3. Handle Errors: Always wrap operations in try/catch blocks
  4. Biometric Auth: Use requireAuthentication for highly sensitive data
  5. Clean Up: Delete sensitive data when no longer needed
  6. 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