Skip to main content

Secure by Default

Launch is configured with security best practices out of the box. Here’s what’s protected and how.

Prerequisites

  • Expo SecureStore installed
  • Auth client configured

Steps

Authentication & Token Storage

Auth tokens are stored securely using expo-secure-store
// lib/auth/client.ts
import * as SecureStore from "expo-secure-store";

export const authClient = createAuthClient({
  plugins: [
    expoClient({
      scheme: "launch",
      storagePrefix: "launch",
      storage: SecureStore, // ✅ Secure storage
    }),
  ],
});
This means:
  • iOS: Tokens are stored in the Keychain (hardware-backed encryption)
  • Android: Tokens are stored in the Keystore (hardware-backed encryption)
Even on jailbroken/rooted devices, this data is significantly harder to extract than plaintext storage.

Choosing the Right Storage

Both AsyncStorage and SecureStore have their place. The key is knowing what data belongs where.

AsyncStorage

Unencrypted key-value storage. Fast and simple, but readable on compromised devices.

SecureStore

Encrypted via native Keychain/Keystore. Hardware-backed protection for sensitive data.

When to Use AsyncStorage ✅

AsyncStorage is perfectly fine for non-sensitive, non-PII data:
Use CaseExampleWhy It’s OK
Theme preferences"dark" or "light"No user impact if exposed
App settings{ notifications: true }Non-personal configuration
Onboarding state{ hasSeenIntro: true }No security implications
Cache dataRecently viewed itemsImproves UX, not sensitive
Feature flags{ betaFeatures: true }Non-sensitive app state
Upload queueProgress tracking metadataNo credentials involved
// ✅ These are fine in AsyncStorage
import AsyncStorage from "@react-native-async-storage/async-storage";

await AsyncStorage.setItem("@theme", "dark");
await AsyncStorage.setItem("@onboarding_complete", "true");
await AsyncStorage.setItem("@last_viewed_category", "electronics");

When to Use SecureStore 🔒

Use SecureStore for anything that could harm the user if exposed:
Use CaseExampleWhy It Needs Protection
Auth tokensJWT, session tokensEnables account access
PasswordsUser credentialsDirect account compromise
API keysThird-party secretsService abuse
Payment dataCard numbers, CVVFinancial fraud
PIISSN, passport, DOBIdentity theft
Biometric dataFace/fingerprint hashesPrivacy violation
// ✅ Use SecureStore for sensitive data
import * as SecureStore from "expo-secure-store";

await SecureStore.setItemAsync("authToken", token);
await SecureStore.setItemAsync("refreshToken", refreshToken);

What Launch Stores Where

DataStorageWhy
Auth tokensSecureStoreSensitive - enables account access
Session dataSecureStoreSensitive - authentication state
Color scheme preferenceAsyncStorageNon-sensitive UI preference
Upload queue stateAsyncStorageNon-sensitive progress tracking

Quick Decision Guide

Ask yourself: “If someone read this data, could they harm the user?”
  • No → AsyncStorage is fine
  • Yes → Use SecureStore
Never store in AsyncStorage: - Authentication tokens or session IDs - Passwords, PINs, or security codes - API keys or secrets - Credit card numbers or financial data - Personal identification (SSN, passport, driver’s license)
  • Health or medical information

Troubleshooting

  • Tokens not stored: check SecureStore permissions
  • Unexpected logout: verify session persistence and SecureStore availability

Next Steps


Best Practices

Be Mindful About What You Store

Before storing any data locally, consider its sensitivity:
// ✅ Non-sensitive data → AsyncStorage
import AsyncStorage from "@react-native-async-storage/async-storage";
await AsyncStorage.setItem("@theme", "dark");
await AsyncStorage.setItem("@language", "en");
await AsyncStorage.setItem("@has_rated_app", "true");

// ✅ Sensitive data → SecureStore
import * as SecureStore from "expo-secure-store";
await SecureStore.setItemAsync("authToken", token);
await SecureStore.setItemAsync("apiKey", apiKey);

Think Before You Store

When adding features that persist data, ask:
  1. Could this data harm the user if exposed? → SecureStore
  2. Could this data impersonate the user? → SecureStore
  3. Is this financial or personal information? → SecureStore
  4. Is this just a preference or app state? → AsyncStorage is fine

Audit Storage Periodically

Search your codebase to review what’s being stored:
# Find all AsyncStorage usage
grep -r "AsyncStorage" --include="*.tsx" --include="*.ts" apps/mobile/

# Find all SecureStore usage
grep -r "SecureStore" --include="*.tsx" --include="*.ts" apps/mobile/
Ensure sensitive data isn’t accidentally stored in AsyncStorage.

What Happens on Jailbroken/Rooted Devices

AsyncStorage Exposure

On a jailbroken iOS device or rooted Android device:
# Attacker can simply read:
/var/mobile/Containers/Data/Application/{APP_ID}/Documents/AsyncStorage/

# Contents are plain JSON:
{"@color_scheme_preference": "dark"}  # ← This is fine
{"@auth_token": "eyJhbGc..."}         # ← This would be BAD

SecureStore Protection

SecureStore data is stored in:
  • iOS: Keychain (encrypted, access-controlled)
  • Android: Keystore (hardware-backed encryption)
Even with root access, extracting Keychain data requires:
  • Specialized forensic tools
  • Device-specific exploits
  • Significantly more effort and expertise
SecureStore isn’t impenetrable on jailbroken devices, but it raises the bar dramatically compared to AsyncStorage.

Additional Mobile Security Measures

Remove Sensitive Console Logs

Before production builds, remove any console.log statements that output sensitive data:
// ❌ Remove before production
console.log("User token:", authToken);
console.log("API response:", response);

// ✅ Use proper logging with levels
if (__DEV__) {
  console.log("Debug info:", safeData);
}

Consider Certificate Pinning (High-Security Apps)

For apps handling financial data or highly sensitive information:
// Using react-native-ssl-pinning or similar
import { fetch } from "react-native-ssl-pinning";

const response = await fetch(url, {
  sslPinning: {
    certs: ["cert1", "cert2"],
  },
});

Consider Jailbreak Detection (Financial Apps)

For banking or payment apps, consider detecting compromised devices:
import JailMonkey from "jail-monkey";

if (JailMonkey.isJailBroken()) {
  // Warn user or restrict functionality
}
Jailbreak detection can be bypassed by determined attackers. Use it as one layer in defense-in-depth, not as your only protection.

Quick Security Audit

Run this checklist for your app:
1

Search for AsyncStorage

Find all uses: grep -r "AsyncStorage" apps/mobile/
2

Verify no sensitive data

Check each usage stores only non-sensitive data
3

Confirm SecureStore for auth

Verify lib/auth/client.ts uses storage: SecureStore
4

Remove sensitive logs

Search for console.log with tokens, passwords, or user data
5

Check third-party SDKs

Verify payment SDKs (Stripe, RevenueCat) use secure storage

Summary

AspectLaunch DefaultStatus
Auth token storageexpo-secure-store✅ Secure
Session managementBetter Auth + SecureStore✅ Secure
User preferencesAsyncStorage✅ OK (non-sensitive)
Upload progressAsyncStorage✅ OK (non-sensitive)
API credentialsEnvironment variables (server-side)✅ Secure
Launch is secure by default. Authentication tokens and sensitive session data are stored using expo-secure-store, which provides hardware-backed encryption on both iOS and Android.