Skip to main content

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');
Note: SecureStore uses platform-specific secure storage (Keychain on iOS, Keystore on Android) and is suitable for sensitive data like authentication tokens or passwords.

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
Note: Always sanitize and validate user input on both client and server to prevent injection attacks.
Warning: Never trust client-side validation alone. Server-side validation is mandatory for security.

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');
}
Note: Expo automatically handles secure connections for production builds. Ensure your app's configuration does not disable HTTPS.
Warning: Never send sensitive data over HTTP in production builds.
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
Note: Ensure that the certificate fingerprint matches exactly what is expected from your server to avoid connection failures.
Warning: If the server's certificate changes, you must update the pinned fingerprint to prevent app crashes.

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)
Note: Never commit API keys to version control. Use environment variables or secure storage mechanisms.
Warning: API keys stored in client-side code can be exposed to end users. Use server-side APIs or secure backend services for sensitive operations.

Best Practices for API Key Management

  • Store keys in .env files 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};
Note: Ensure that your OAuth provider supports mobile app redirect URIs and has proper security headers set.
Warning: Never store sensitive OAuth credentials in client-side code. Always use a backend server to handle token refresh and validation.

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');
  }
};
Note: Ensure you have the necessary permissions in your app's configuration for biometric access.

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
Note: This feature works on both iOS and Android, but may not be effective against all third-party screen recording tools.

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
  }
);
Note: Obfuscation does not guarantee complete security and can impact app performance. It is recommended to use it in conjunction with other security measures like secure API keys and server-side validation.

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

Example: Enabling App Attestation

{
  "expo": {
    "appAttestation": true
  }
}
Note: App Attestation is currently in beta and requires the latest Expo SDK version.
Warning: Enabling App Attestation may impact app performance and should be tested thoroughly in a staging environment before deploying to production.
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
{
  "expo": {
    "scheme": "myapp",
    "intentFilters": [
      {
        "action": "android.intent.action.VIEW",
        "category": ["android.intent.category.DEFAULT", "android.intent.category.BROWSABLE"],
        "data": {
          "scheme": "myapp"
        }
      }
    ]
  }
}
Note: Always validate deep link parameters on the server side to prevent injection attacks.
Warning: Avoid using generic schemes like 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'
Note: SecureStore is backed by the device's secure storage and is not accessible to other apps or users without proper permissions.
Warning: Never store sensitive data in AsyncStorage or localStorage as they are not encrypted and can be accessed by malicious apps.

Best Practices for Local Data Encryption

  • Use SecureStore for sensitive data such as authentication tokens, passwords, or API keys.
  • For large files or structured data, consider combining FileSystem with encryption libraries like expo-crypto.
  • Avoid storing sensitive data in plain text or unencrypted formats on the device.

Was this article helpful?