Implementing Security Best Practices
1. Storing Sensitive Data
| Storage Method | Description | Security Level | Notes |
|---|---|---|---|
| AsyncStorage | Key-value storage for small amounts of data | DEPRECATED | Use SecureStore instead for sensitive data |
| SecureStore | Encrypted storage for sensitive data like tokens or passwords | High | Available via @expo/secure-store |
| FileSystem | Read/write files to device storage | Medium | Use with encryption for sensitive content |
Example: Using SecureStore to save and retrieve a token
import * as SecureStore from 'expo-secure-store';
// Save token
await SecureStore.setItemAsync('authToken', 'abc123xyz');
// Retrieve token
const token = await SecureStore.getItemAsync('authToken');
2. Validating User Input
| Input Type | Validation Method | Description |
|---|---|---|
| Text Fields | RegExp |
Use regex patterns to validate format (e.g., email, phone number) |
| Numeric Fields | Number.isInteger() |
Ensure input is a valid integer or float |
| Date Fields | Date.parse() |
Validate date format and existence |
| File Uploads | mime-types |
Check MIME type and file extension for allowed formats |
Example: Validating Email Input
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const isValidEmail = (email) => emailRegex.test(email);
console.log(isValidEmail("user@example.com")); // true
Example: Validating Numeric Input
const validateNumber = (input) => {
const num = parseFloat(input);
return !isNaN(num) && isFinite(num);
};
console.log(validateNumber("123.45")); // true
3. Using HTTPS Connections
| Property | Description | Value |
|---|---|---|
| expo-constants | Provides access to app constants including the app's URL | https://exp.host/@username/app-name |
| expo-network | Provides network status information | true if connected securely |
Example: Enforcing HTTPS in Expo
import Constants from 'expo-constants';
const appUrl = Constants.expoVersion;
if (appUrl.startsWith('https')) {
console.log('Secure connection active');
} else {
console.warn('Insecure connection detected');
}
| Configuration | Value | Description |
|---|---|---|
| scheme | https |
Enforces secure protocol for app links |
| allowArbitraryLoads | false |
Prevents loading insecure content |
4. Implementing Certificate Pinning
Certificate pinning is a security technique that ensures your app only accepts connections from specific certificates or public keys, mitigating man-in-the-middle (MITM) attacks.
| Property | Description | Value |
|---|---|---|
| expo-crypto | Used to verify server certificates against pinned values | true |
| pinningConfig | Configuration object specifying pinned certificates or public keys | {} |
Example: Setting up Certificate Pinning with expo-crypto
import { getCrypto } from 'expo-crypto';\n\nconst pinningConfig = {\n server: {\n certificateFingerprint: 'SHA256/ABC123...',\n },\n};\n\n// Use pinningConfig in network requests
5. Securing API Keys
Example: Environment Variables in Expo
import Constants from 'expo-constants';
const apiKey = Constants.expoConfig?.extra?.apiKey;
if (apiKey) {
console.log('API key loaded securely');
} else {
console.log('API key not found');
}
| Configuration | Value | Description |
|---|---|---|
| EXPO_PUBLIC_API_KEY | your-api-key-here |
Public API key for client-side use (e.g., third-party services) |
| EXPO_PRIVATE_API_KEY | your-private-api-key-here |
Private API key for server-side use (e.g., backend services) |
Best Practices for API Key Management
- Store keys in
.envfiles or secure cloud services. - Use environment variables to inject keys at build time.
- Implement server-side proxying for sensitive API calls.
- Rotate keys regularly and monitor usage patterns.
6. Using OAuth 2.0
| Feature | Description | Notes |
|---|---|---|
| Authorization Code Flow | Used for server-side applications to obtain access tokens. | Most secure flow; requires a backend server to handle token exchange. |
| Implicit Flow | Used for client-side applications (e.g., single-page apps) that cannot securely store a client secret. | Less secure; tokens are returned directly to the browser. |
| Resource Owner Password Credentials | Directly exchanges user credentials for an access token. | Only recommended if other flows are not feasible; high security risk. |
| Client Credentials | Used for machine-to-machine communication where the client is the resource owner. | No user interaction required; suitable for backend services. |
Example: Setting up OAuth 2.0 with Expo
import * as WebBrowser from 'expo-web-browser';\n\nconst handleLogin = async () => {\n const result = await WebBrowser.openAuthSessionAsync(\n 'https://example.com/oauth/authorize',\n 'https://example.com/callback'\n );\n\n if (result.type === 'success') {\n const { url } = result;\n // Extract token from URL fragment or query params\n }\n};
7. Implementing Biometric Auth
| Property | Description | Notes |
|---|---|---|
| Expo.LocalAuthentication | Module for biometric authentication | Available on iOS and Android |
| hasHardwareAsync() | Checks if device supports biometric hardware | Returns boolean |
| isEnrolledAsync() | Checks if biometric is enrolled | Returns boolean |
| authenticateAsync() | Initiates biometric authentication | Supports Face ID, Touch ID, and fingerprint |
Example: Check biometric support and authenticate
import * as LocalAuthentication from 'expo-local-authentication';
const authenticateUser = async () => {
const hasHardware = await LocalAuthentication.hasHardwareAsync();
if (!hasHardware) {
console.log('Biometric hardware not supported');
return;
}
const isEnrolled = await LocalAuthentication.isEnrolledAsync();
if (!isEnrolled) {
console.log('No biometric enrolled');
return;
}
const authResult = await LocalAuthentication.authenticateAsync({
promptMessage: 'Authenticate with biometrics',
cancelLabel: 'Cancel',
});
if (authResult.success) {
console.log('Authentication successful');
} else {
console.log('Authentication failed');
}
};
8. Preventing Screen Capture
| Property | Description | Notes |
|---|---|---|
| expo-screen-capture | Prevents screen capture and screenshot functionality | Available in Expo SDK 51+ |
Example: Disabling Screen Capture
import { preventScreenCapture } from 'expo-screen-capture';
preventScreenCapture(true); // Disable screen capture
preventScreenCapture(false); // Re-enable screen capture
9. Using Code Obfuscation
Code obfuscation in Expo helps protect your application's source code from being easily readable or reverse-engineered. While Expo does not provide built-in obfuscation tools, you can integrate third-party solutions or use build-time configurations to achieve this.
| Method | Description | Notes |
|---|---|---|
| Third-party Obfuscators | Tools like javascript-obfuscator can be used to obfuscate JavaScript code. |
Requires integration into the build pipeline |
| Build-time Configuration | Configure obfuscation as part of your build process using tools like Webpack or Metro. | May require custom configurations |
Example: Integrating javascript-obfuscator
// Install the obfuscator
npm install javascript-obfuscator --save-dev
// Example configuration in webpack.config.js or build script
const Obfuscator = require('javascript-obfuscator');
const obfuscatedCode = Obfuscator.obfuscate(
'console.log("Hello World");',
{
compact: true,
controlFlowFlattening: true,
controlFlowFlatteningThreshold: 1,
deadCodeInjection: true,
deadCodeInjectionThreshold: 1,
debugProtection: false,
debugProtectionInterval: 0,
disableConsoleOutput: true,
identifierNamesGenerator: 'hexadecimal',
log: false,
renameGlobals: false,
reservedNames: [],
reservedStrings: [],
rotateStringArray: true,
seed: 0,
shuffleStringArray: true,
splitStrings: true,
splitStringsChunkLength: 10,
stringArray: true,
stringArrayEncoding: [],
stringArrayIndexShift: 0,
stringArrayRotate: true,
stringArrayShuffle: true,
stringArrayThreshold: 1,
unicodeEscapeSequence: false
}
);
10. Implementing App Attestation
App Attestation is a security feature that helps verify the integrity of your Expo application at runtime. It ensures that the app hasn't been tampered with or modified in an unauthorized way.
| Feature | Description | Status |
|---|---|---|
| App Attestation | Verifies the integrity of the app at runtime by checking the app's signature and configuration. | NEW |
11. Handling Deep Link Security
| Property | Description | Security Impact |
|---|---|---|
| expo-linking | Library for handling deep links in Expo apps | Ensures proper URL scheme validation and routing |
| scheme | URL scheme defined in app.json |
Prevents unauthorized app launches via external links |
| intentFilters | Android-specific deep link filters in app.json |
Controls which URLs can open the app on Android |
Example: Configuring Deep Link Schemes
{
"expo": {
"scheme": "myapp",
"intentFilters": [
{
"action": "android.intent.action.VIEW",
"category": ["android.intent.category.DEFAULT", "android.intent.category.BROWSABLE"],
"data": {
"scheme": "myapp"
}
}
]
}
}
exp or expo in production apps.
| Security Measure | Description | Implementation |
|---|---|---|
| URL Validation | Validate incoming URLs before processing | Use regex or URL parsing libraries to sanitize input |
| Domain Whitelisting | Allow only known domains to trigger deep links | Implement a whitelist in your app's linking configuration |
| Secure Parameters | Pass sensitive data through secure channels | Avoid passing credentials or tokens in URL parameters |
12. Encrypting Local Data
Expo provides several mechanisms to securely store and encrypt local data on the device. These methods help protect sensitive information from unauthorized access.
| Method | Description | Notes |
|---|---|---|
| AsyncStorage | Stores data as key-value pairs in a persistent storage system. | Not encrypted by default. Requires additional encryption layer for sensitive data. |
| SecureStore | Stores data in an encrypted key-value store using the device's secure storage. | Recommended for storing sensitive data like tokens, passwords, or API keys. Uses hardware-level encryption on iOS and Android. |
| FileSystem | Allows reading and writing files to the device's file system. | Can be used with encryption libraries such as react-native-crypto or expo-crypto for secure file handling. |
Example: Using SecureStore to Store and Retrieve Data
import * as SecureStore from 'expo-secure-store';
// Store a value
await SecureStore.setItemAsync('authToken', 'abc123xyz');
// Retrieve a value
const token = await SecureStore.getItemAsync('authToken');
console.log(token); // 'abc123xyz'
AsyncStorage or localStorage as they are not encrypted and can be accessed by malicious apps.
Best Practices for Local Data Encryption
- Use
SecureStorefor sensitive data such as authentication tokens, passwords, or API keys. - For large files or structured data, consider combining
FileSystemwith encryption libraries likeexpo-crypto. - Avoid storing sensitive data in plain text or unencrypted formats on the device.