Skip to main content

React Native Security Implementation

React Native security implementations using native security APIs through JavaScript bridges.

Overview

This guide covers React Native-specific security implementations. For security concepts and principles, see Security Overview. For platform-native implementations, see Android Security and iOS Security.

Core Focus Areas:

  • react-native-keychain for secure credential storage
  • react-native-biometrics for Face ID/Touch ID/Fingerprint
  • Certificate pinning with native modules
  • Code obfuscation (ProGuard/Hermes)
  • TypeScript security patterns
Related Security Topics

Security Threat Model

React Native banking applications face multiple threat vectors. Device malware (screen recorders, keyloggers) can capture sensitive data displayed or entered in the app - mitigate with screenshot blocking and avoiding sensitive data in logs. Network attackers (man-in-the-middle) can intercept API traffic - mitigate with certificate pinning and short-lived tokens. Physical device access (stolen phones) can compromise stored credentials - mitigate with biometric-protected Keychain storage using WHEN_UNLOCKED_THIS_DEVICE_ONLY.

The core principle: never trust the client. All sensitive operations must be validated server-side. The React Native app can be decompiled, debugged, and modified by determined attackers. Code obfuscation (ProGuard for Android, Hermes bytecode) raises the bar but isn't impenetrable. Therefore, authentication tokens must be short-lived, biometric success should trigger server-side verification, and payment amounts must be validated server-side even though the app validates them client-side for UX.

Native platform security APIs (iOS Keychain, Android Keystore) provide hardware-backed encryption. When you store a token in Keychain with WHEN_UNLOCKED_THIS_DEVICE_ONLY, it's encrypted with hardware-derived keys and can't be extracted even on rooted/jailbroken devices (on modern devices with Secure Enclave/StrongBox). This is fundamentally more secure than JavaScript-based encryption because keys never exist in JavaScript-accessible memory. For encryption strategies, see Data Protection.


Security Architecture


Secure Storage with Keychain

// services/storage/secureStorage.ts
import * as Keychain from 'react-native-keychain';

/**
* Secure storage for sensitive data using native Keychain
*/
export const secureStorage = {
/**
* Store auth token securely
*/
async setToken(token: string): Promise<void> {
await Keychain.setGenericPassword('auth_token', token, {
service: 'com.bank.payment',
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});
},

/**
* Retrieve auth token
*/
async getToken(): Promise<string | null> {
const credentials = await Keychain.getGenericPassword({
service: 'com.bank.payment',
});

return credentials ? credentials.password : null;
},

/**
* Delete auth token
*/
async deleteToken(): Promise<void> {
await Keychain.resetGenericPassword({
service: 'com.bank.payment',
});
},

/**
* Store biometric credentials
*/
async setBiometricCredentials(username: string, password: string): Promise<void> {
await Keychain.setGenericPassword(username, password, {
service: 'com.bank.payment.biometric',
accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_CURRENT_SET,
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});
},

/**
* Retrieve biometric credentials (triggers biometric prompt)
*/
async getBiometricCredentials(): Promise<{ username: string; password: string } | null> {
const credentials = await Keychain.getGenericPassword({
service: 'com.bank.payment.biometric',
authenticationPrompt: {
title: 'Authenticate',
subtitle: 'Verify your identity',
},
});

if (!credentials) return null;

return {
username: credentials.username,
password: credentials.password,
};
},
};

Biometric Authentication

Biometric authentication (Face ID, Touch ID, fingerprint, face unlock) provides convenient security by verifying user identity through biological traits. The underlying mechanism uses the Secure Enclave (iOS) or StrongBox/Trusted Execution Environment (Android) to perform biometric matching in hardware isolated from the main operating system. Your application never receives the actual biometric data; it only receives a success/failure result.

When implementing biometric authentication for banking, understand the difference between device unlock biometrics and payment authentication biometrics. For simple app unlock ("quick login"), biometric success can retrieve stored credentials from Keychain and auto-login. For payment confirmation, you must create a cryptographic signature using biometric-protected keys and send that signature to the server for verification. This prevents a compromised device from approving payments by bypassing the biometric check.

The ACCESS_CONTROL.BIOMETRY_CURRENT_SET setting is crucial: it invalidates stored credentials if biometric settings change (new fingerprint added, Face ID re-enrolled). This prevents an attacker who adds their fingerprint to a stolen phone from accessing banking credentials. The downside: legitimate users who re-enroll biometrics must re-authenticate, but this security trade-off is appropriate for banking applications.

// services/auth/biometricService.ts
import ReactNativeBiometrics from 'react-native-biometrics';

const rnBiometrics = new ReactNativeBiometrics();

export const biometricService = {
/**
* Check if biometric sensors are available on device
* Returns biometry type: FaceID, TouchID, Biometrics (Android)
*/
async isAvailable(): Promise<boolean> {
const { available, biometryType } = await rnBiometrics.isSensorAvailable();

if (available && biometryType) {
console.log(`Biometric type: ${biometryType}`);
return true;
}

return false;
},

/**
* Prompt for biometric authentication
*/
async authenticate(reason: string = 'Verify your identity'): Promise<boolean> {
try {
const { success } = await rnBiometrics.simplePrompt({
promptMessage: reason,
cancelButtonText: 'Cancel',
});

return success;
} catch (error) {
console.error('Biometric authentication failed:', error);
return false;
}
},

/**
* Create biometric signature for transactions
*/
async createSignature(payload: string): Promise<{ signature: string } | null> {
try {
const { success, signature } = await rnBiometrics.createSignature({
promptMessage: 'Confirm payment',
payload,
});

return success && signature ? { signature } : null;
} catch (error) {
console.error('Failed to create signature:', error);
return null;
}
},
};

// Usage in login flow
export default function BiometricLoginScreen() {
const handleBiometricLogin = async () => {
const isAvailable = await biometricService.isAvailable();

if (!isAvailable) {
Alert.alert('Biometrics not available');
return;
}

const authenticated = await biometricService.authenticate();

if (authenticated) {
const credentials = await secureStorage.getBiometricCredentials();
if (credentials) {
await login(credentials.username, credentials.password);
}
}
};

return (
<Button
title="Login with Face ID"
onPress={handleBiometricLogin}
/>
);
}

Certificate Pinning

// services/api/certificatePinning.ts
import axios from 'axios';
import { Platform } from 'react-native';

/**
* Certificate pinning configuration
* Prevents man-in-the-middle attacks
*/
export const setupCertificatePinning = () => {
if (Platform.OS === 'ios') {
// iOS: Configure in Info.plist
// App Transport Security Settings
} else {
// Android: Configure in network_security_config.xml
/*
<network-security-config>
<domain-config>
<domain includeSubdomains="true">api.bank.com</domain>
<pin-set>
<pin digest="SHA-256">base64_encoded_public_key_hash</pin>
</pin-set>
</domain-config>
</network-security-config>
*/
}
};

// Axios configuration with SSL pinning library
import { SSLPinningPlugin } from 'react-native-ssl-pinning';

export const apiClient = axios.create({
baseURL: 'https://api.bank.com',
timeout: 10000,
});

// Add certificate pinning
apiClient.interceptors.request.use(
async (config) => {
if (Platform.OS === 'android') {
config.sslPinning = {
certs: ['sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='],
};
}
return config;
}
);

Preventing Screenshots

// App.tsx or Screen component
import { useEffect } from 'react';
import { Platform } from 'react-native';
import ScreenGuard from 'react-native-screenguard';

export default function PaymentConfirmScreen() {
useEffect(() => {
// Prevent screenshots on sensitive screens
if (Platform.OS === 'android') {
ScreenGuard.register();
} else {
// iOS: Set FLAG_SECURE in native code
ScreenGuard.activateShield();
}

return () => {
if (Platform.OS === 'android') {
ScreenGuard.unregister();
} else {
ScreenGuard.deactivateShield();
}
};
}, []);

return (
<View>
<Text>Confirm payment of $1,000.00</Text>
<Button title="Confirm" onPress={handleConfirm} />
</View>
);
}

Code Obfuscation

// android/app/proguard-rules.pro
# Keep payment-related classes from obfuscation
-keep class com.bank.payment.models.** { *; }
-keep class com.bank.payment.services.** { *; }

# React Native
-keep class com.facebook.react.** { *; }
-keep class com.facebook.hermes.** { *; }

# Remove logging in production
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** v(...);
public static *** i(...);
}
// metro.config.js
module.exports = {
transformer: {
minifierConfig: {
keep_classnames: false,
keep_fnames: false,
mangle: {
toplevel: true,
},
compress: {
drop_console: true, // Remove console.log in production
},
},
},
};

Common Mistakes

Storing Tokens in AsyncStorage

// BAD: Unencrypted storage
await AsyncStorage.setItem('auth_token', token);
// GOOD: Use Keychain
await secureStorage.setToken(token);

No Certificate Pinning

// BAD: Default axios without pinning
const api = axios.create({ baseURL: 'https://api.bank.com' });
// GOOD: Certificate pinning configured
const api = axios.create({
baseURL: 'https://api.bank.com',
sslPinning: { certs: ['sha256/...'] },
});

Code Review Checklist

Secure Storage

  • Keychain used for tokens and credentials
  • AsyncStorage only for non-sensitive data
  • Biometric protection for stored credentials
  • Proper access control (WHEN_UNLOCKED_THIS_DEVICE_ONLY)

Authentication

  • Biometric availability checked before use
  • Fallback authentication available (PIN/password)
  • Session timeout implemented
  • Token refresh handled automatically
  • Logout clears all secure storage

Network Security

  • Certificate pinning enabled for API calls
  • HTTPS only (no HTTP fallback)
  • SSL errors not ignored
  • Public key hashes configured correctly
  • Certificate rotation plan in place

Screen Security

  • Screenshot prevention on sensitive screens
  • Screen recording blocked on payment screens
  • Blur app when backgrounded
  • Biometric re-auth on app resume

Code Security

  • ProGuard enabled for Android release builds
  • Hermes enabled for obfuscation
  • Console logs removed in production
  • Sensitive data not logged
  • Debug mode disabled in release

Further Reading

React Native Framework Guidelines

Security Guidelines

Mobile-Specific Guidelines

External Resources


Summary

Key Takeaways

  1. Keychain for sensitive data - Never use AsyncStorage for tokens
  2. Biometric authentication - Face ID/Touch ID for secure login
  3. Certificate pinning - Prevent man-in-the-middle attacks
  4. Screenshot prevention - Block screenshots on sensitive screens
  5. Code obfuscation - ProGuard and Hermes for release builds
  6. Session management - Timeout and re-auth on resume
  7. Secure communication - HTTPS only with pinned certificates
  8. Access control - WHEN_UNLOCKED_THIS_DEVICE_ONLY
  9. Token refresh - Automatic token renewal
  10. Remove logs - No console.log in production

Next Steps: Review React Native Performance for optimization strategies.