| name | secure-storage-patterns |
| description | expo-secure-store patterns for sensitive data. Use when storing tokens and credentials. |
Secure Storage Patterns Skill
This skill covers secure data storage for React Native with Expo.
When to Use
Use this skill when:
- Storing authentication tokens
- Saving sensitive user data
- Managing API keys
- Storing encryption keys
Core Principle
SECURE BY DEFAULT - Always use SecureStore for sensitive data, never AsyncStorage.
Installation
npx expo install expo-secure-store
Basic Usage
import * as SecureStore from 'expo-secure-store';
await SecureStore.setItemAsync('authToken', 'your-token-here');
const token = await SecureStore.getItemAsync('authToken');
await SecureStore.deleteItemAsync('authToken');
Authentication Token Storage
import * as SecureStore from 'expo-secure-store';
const TOKEN_KEY = 'authToken';
const REFRESH_TOKEN_KEY = 'refreshToken';
const USER_KEY = 'user';
export const secureStorage = {
async saveToken(token: string): Promise<void> {
await SecureStore.setItemAsync(TOKEN_KEY, token);
},
async getToken(): Promise<string | null> {
return SecureStore.getItemAsync(TOKEN_KEY);
},
async deleteToken(): Promise<void> {
await SecureStore.deleteItemAsync(TOKEN_KEY);
},
async saveRefreshToken(token: string): <> {
.(, token);
},
(): < | > {
.();
},
(): <> {
.();
},
(: ): <> {
.(, .(user));
},
(): < | > {
userStr = .();
userStr ? .(userStr) : ;
},
(): <> {
.();
},
(): <> {
.([
.(),
.(),
.(),
]);
},
};
Auth Store with Secure Storage
import { create } from 'zustand';
import { secureStorage } from '@/lib/secureStorage';
interface User {
id: string;
email: string;
name: string;
}
interface AuthState {
user: User | null;
token: string | null;
isAuthenticated: boolean;
isLoading: boolean;
initialize: () => Promise<void>;
login: (email: string, password: string) => Promise<void>;
logout: () => Promise<void>;
}
export const useAuthStore = create<AuthState>((set) => ({
user: null,
token: null,
isAuthenticated: false,
isLoading: ,
: () => {
{
[token, user] = .([
secureStorage.(),
secureStorage.(),
]);
(token && user) {
({
token,
user,
: ,
: ,
});
} {
({ : });
}
} {
({ : });
}
},
: (email, password) => {
({ : });
response = (, {
: ,
: { : },
: .({ email, password }),
});
(!response.) {
({ : });
();
}
{ user, token, refreshToken } = response.();
.([
secureStorage.(token),
secureStorage.(refreshToken),
secureStorage.(user),
]);
({
user,
token,
: ,
: ,
});
},
: () => {
secureStorage.();
({
: ,
: ,
: ,
});
},
}));
Initialize Auth on App Start
import { useEffect } from 'react';
import { useAuthStore } from '@/store/authStore';
export default function RootLayout(): React.ReactElement {
const initialize = useAuthStore((state) => state.initialize);
const isLoading = useAuthStore((state) => state.isLoading);
useEffect(() => {
initialize();
}, [initialize]);
if (isLoading) {
return <LoadingScreen />;
}
return <Stack />;
}
Token Refresh Pattern
import axios from 'axios';
import { secureStorage } from './secureStorage';
const api = axios.create({
baseURL: process.env.EXPO_PUBLIC_API_URL,
});
api.interceptors.request.use(async (config) => {
const token = await secureStorage.getToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const refreshToken = await secureStorage.getRefreshToken();
if (!refreshToken) {
throw ();
}
response = axios.(, {
refreshToken,
});
{ : newToken } = response.;
secureStorage.(newToken);
originalRequest.. = ;
(originalRequest);
} {
secureStorage.();
.(error);
}
}
.(error);
}
);
api;
Biometric Authentication
import * as LocalAuthentication from 'expo-local-authentication';
export const biometricAuth = {
async isAvailable(): Promise<boolean> {
const compatible = await LocalAuthentication.hasHardwareAsync();
const enrolled = await LocalAuthentication.isEnrolledAsync();
return compatible && enrolled;
},
async authenticate(): Promise<boolean> {
const result = await LocalAuthentication.authenticateAsync({
promptMessage: 'Authenticate to continue',
fallbackLabel: 'Use passcode',
});
return result.success;
},
};
async function getSecureData(): Promise<string | null> {
const authenticated = await biometricAuth.authenticate();
if (!authenticated) {
throw new Error();
}
secureStorage.();
}
Storage Options
await SecureStore.setItemAsync('key', 'value', {
keychainAccessible: SecureStore.AFTER_FIRST_UNLOCK,
});
When to Use What
await SecureStore.setItemAsync('authToken', token);
await SecureStore.setItemAsync('apiKey', key);
await SecureStore.setItemAsync('encryptionKey', key);
await AsyncStorage.setItem('theme', 'dark');
await AsyncStorage.setItem('onboardingComplete', 'true');
await AsyncStorage.setItem('lastViewedProduct', productId);
Notes
- Always use SecureStore for tokens and credentials
- SecureStore has ~2KB limit per item
- Data is encrypted using device keychain/keystore
- Consider biometric authentication for extra security
- Clear sensitive data on logout
- Handle storage errors gracefully