React Native Privacy & Security
Privacy-first patterns for sensitive data handling in React Native apps.
Biometric Authentication
Setup (expo-local-authentication)
import * as LocalAuthentication from 'expo-local-authentication';
async function authenticateWithBiometrics(): Promise<boolean> {
const hasHardware = await LocalAuthentication.hasHardwareAsync();
if (!hasHardware) return false;
const isEnrolled = await LocalAuthentication.isEnrolledAsync();
if (!isEnrolled) return false;
const result = await LocalAuthentication.authenticateAsync({
promptMessage: 'Authenticate to access your private data',
fallbackLabel: 'Use passcode',
disableDeviceFallback: false,
cancelLabel: 'Cancel',
});
return result.success;
}
App Lock Pattern
function PrivacyGate({ children }: { children: React.ReactNode }) {
const [authenticated, setAuthenticated] = useState(false);
const { isEnabled } = usePrivacySettings();
useEffect(() => {
if (!isEnabled) {
setAuthenticated(true);
return;
}
checkAuth();
}, [isEnabled]);
async function checkAuth() {
const success = await authenticateWithBiometrics();
setAuthenticated(success);
}
if (!authenticated) {
return <AuthPrompt />;
}
return children;
}
Secure Storage Patterns
Key Storage
import * as SecureStore from 'expo-secure-store';
// Store encryption keys (NOT in AsyncStorage!)
await SecureStore.setItemAsync('encryption_key', key);
// Retrieve
const key = await SecureStore.getItemAsync('encryption_key');
// Delete on logout
await SecureStore.deleteItemAsync('encryption_key');
What Goes Where
| Storage | Use For | Never Store |
|---|---|---|
| SecureStore | Encryption keys, auth tokens, PII | Large data |
| AsyncStorage | UI state, cache, preferences | Keys, tokens, PII |
| SQLite | App data, encrypted content | Unencrypted sensitive data |
| Keychain/Keystore | Credentials, certificates | - |
Data Export (GDPR/CCPA)
async function exportUserData(userId: string): Promise<string> {
const db = await openDatabase();
// Gather all user data
const journal = await db.getAllAsync('SELECT * FROM journal WHERE user_id = ?', userId);
const checkins = await db.getAllAsync('SELECT * FROM checkins WHERE user_id = ?', userId);
// Decrypt what can be decrypted
const decryptedJournal = await Promise.all(
journal.map(async (entry) => ({
...entry,
content: await decryptContent(entry.encrypted_body).catch(() => '[encrypted]'),
})),
);
const exportData = {
exported_at: new Date().toISOString(),
user_id: userId,
journal: decryptedJournal,
checkins,
// ... other tables
};
return JSON.stringify(exportData, null, 2);
}
async function shareDataExport(): Promise<void> {
const data = await exportUserData(currentUser.id);
const fileUri = FileSystem.cacheDirectory + 'export.json';
await FileSystem.writeAsStringAsync(fileUri, data);
await Sharing.shareAsync(fileUri, {
mimeType: 'application/json',
dialogTitle: 'Your Data Export',
});
}
Data Deletion
async function deleteAllUserData(userId: string): Promise<void> {
const db = await openDatabase();
await db.withTransactionAsync(async () => {
// Delete in order (respect FK constraints)
await db.runAsync('DELETE FROM sync_queue WHERE user_id = ?', userId);
await db.runAsync('DELETE FROM journal_entries WHERE user_id = ?', userId);
await db.runAsync('DELETE FROM checkins WHERE user_id = ?', userId);
await db.runAsync('DELETE FROM step_work WHERE user_id = ?', userId);
await db.runAsync('DELETE FROM user_settings WHERE user_id = ?', userId);
});
// Clear secure storage
await SecureStore.deleteItemAsync('encryption_key');
await SecureStore.deleteItemAsync('auth_token');
}
Privacy Settings UI
function PrivacySettings() {
const { settings, updateSetting } = usePrivacySettings();
return (
<View className="space-y-4">
<SettingToggle
title="Biometric Lock"
description="Require Face ID/Touch ID to open app"
value={settings.biometricLock}
=> updateSetting('biometricLock', v)}
/>
<SettingToggle
title="Blur App in Switcher"
description="Hide content in app switcher"
value={settings.blurInSwitcher}
=> updateSetting('blurInSwitcher', v)}
/>
<SettingToggle
title="Analytics"
description="Help improve the app with anonymous usage data"
value={settings.analyticsEnabled}
=> updateSetting('analyticsEnabled', v)}
/>
<Button
title="Export My Data"
variant="outline"
/>
<Button
title="Delete All Data"
variant="danger"
/>
</View>
);
}
App Switcher Privacy
import { useAppState } from '@react-native-community/hooks';
function App({ children }) {
const appState = useAppState();
const [blur, setBlur] = useState(false);
useEffect(() => {
if (appState === 'background') {
setBlur(true);
} else if (appState === 'active') {
// Optional: require auth after background
setBlur(false);
}
}, [appState]);
return (
<View style={{ flex: 1 }}>
{children}
{blur && (
<View
style={StyleSheet.absoluteFill}
className="bg-slate-900 items-center justify-center"
>
<Text className="text-white text-xl">Steps to Recovery</Text>
</View>
)}
</View>
);
}
HIPAA Considerations
// Audit logging for sensitive operations
async function auditLog(action: string, resource: string): Promise<void> {
await db.runAsync(
'INSERT INTO audit_log (user_id, action, resource, timestamp) VALUES (?, ?, ?, ?)',
currentUser.id,
action,
resource,
Date.now(),
);
}
// Usage
async function viewJournalEntry(entryId: string) {
await auditLog('VIEW', `journal:${entryId}`);
// ... view logic
}
async function exportData() {
await auditLog('EXPORT', 'user_data');
// ... export logic
}
Security Checklist
- Encryption keys in SecureStore only
- Biometric auth for sensitive screens
- App switcher blur enabled
- Auto-lock after inactivity
- Data export functionality
- Complete data deletion option
- Audit logging for HIPAA
- No PII in logs/crash reports
- Certificate pinning for API calls
- Sentry scrubbing for sensitive data