Implementing Security Best Practices
1. Storing Sensitive Data Securely
Example: Secure Storage with react-native-sensitive-info
import SInfo from 'react-native-sensitive-info';
// Store sensitive data
await SInfo.setItem('authToken', 'abc123xyz', {
sharedPreferencesName: 'mySharedPrefs',
keychainService: 'myKeychain',
});
// Retrieve sensitive data
const token = await SInfo.getItem('authToken', {
sharedPreferencesName: 'mySharedPrefs',
keychainService: 'myKeychain',
});
| Option | Values | Notes |
|---|---|---|
| sharedPreferencesName | 'mySharedPrefs' |
Android shared preferences name |
| keychainService | 'myKeychain' |
iOS keychain service name |
| accessibility | 'WhenUnlocked' |
Security level for data access |
Note: On Android,
sharedPreferencesName is used for non-encrypted storage. On iOS, keychainService is used for secure keychain storage.Warning: Never store sensitive data in plain text or local storage without encryption.
Secure Storage Best Practices
- Use platform-specific secure storage (Keychain on iOS, Keystore on Android)
- Never store tokens or secrets in
AsyncStorage - Use
react-native-sensitive-infoor similar libraries for cross-platform secure storage - Set appropriate accessibility levels (e.g.,
WhenUnlocked)
2. Implementing SSL Pinning
SSL pinning is a security technique that ensures your app only accepts connections to specific certificates or public keys, preventing man-in-the-middle (MITM) attacks. It's particularly important in mobile apps where network traffic is vulnerable to interception.
| Platform | Implementation Method | Notes |
|---|---|---|
| iOS (React Native) | Use NSAppTransportSecurity in Info.plist |
Requires configuring NSExceptionDomains with pinned certificates or public keys. |
| Android (React Native) | Use Network Security Config |
Define a network-security-config.xml file with <domain> and <pin> elements. |
Example: iOS SSL Pinning Configuration
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>api.example.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<false/>
<key>NSExceptionRequiresForwardSecrecy</key>
<true/>
<key>NSExceptionMinimumTLSVersion</key>
<string>TLSv1.2</string>
<key>NSPinnedCertificates</key>
<array>
<data>MIIE...</data>
</array>
</dict>
</dict>
</dict>
</dict>
</plist>
Example: Android SSL Pinning Configuration
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">api.example.com</domain>
<pin-set expiration="2025-12-31">
<pin digest="SHA-256">MIIE...</pin>
&pin-set>
</domain-config>
</network-security-config>
Note: Ensure that the pinned certificates are updated regularly to avoid breaking connectivity when certificates expire or change.
Warning: Misconfiguring SSL pinning can lead to app crashes or connectivity issues if the server's certificate does not match the pinned one.
3. Using Code Obfuscation
Code obfuscation is a technique used to make React Native application code harder to read and understand, which can help protect against reverse engineering and intellectual property theft.
| Technique | Description | Implementation | Notes |
|---|---|---|---|
| JavaScript Obfuscation | Transforms JavaScript code into a less readable format. | |
Use tools like JavaScript Obfuscator to obfuscate your code before deployment. |
| Native Code Obfuscation | Protects native modules and code by obfuscating them. | |
For Android, use ProGuard or R8 to obfuscate native code. iOS uses similar techniques with symbol mangling. |
Example: Obfuscating React Native Code
// Before obfuscation
const secretKey = 'mySecretKey';
function authenticateUser(username, password) {
if (username === 'admin' && password === secretKey) {
return true;
}
return false;
}
// After obfuscation (simplified)
var _0x123456 = ['secretKey', 'authenticateUser'];
(function (_0x123457, _0x123458) {
var _0x123459 = _0x123456[_0x123458];
return _0x123459;
}(_0x123456, 0));
function _0x12345a(_0x12345b, _0x12345c) {
var _0x12345d = _0x123456[1];
if (_0x12345b === 'admin' && _0x12345c === _0x123456[0]) {
return true;
}
return false;
}
Note: Obfuscation does not provide complete security and can impact app performance. It should be used in conjunction with other security measures.
4. Preventing Reverse Engineering
| Technique | Description | Implementation | Notes |
|---|---|---|---|
| Code Obfuscation | Transforms readable source code into less readable form to hinder reverse engineering. | const obfuscatedCode = "console.log('Hello World')"; |
Use tools like javascript-obfuscator or UglifyJS. |
| Native Module Protection | Implement security in native modules to prevent code inspection. | |
Use React Native with native modules for sensitive logic. |
| Runtime Checks | Verify app integrity at runtime to detect tampering or debugging. | |
Use __DEV__ flag to detect development environments. |
Example: Runtime Integrity Check
import { Platform } from 'react-native';
const isDebugging = () => {
if (Platform.OS === 'android') {
return __DEV__ || false;
}
return __DEV__ || false;
};
if (isDebugging()) {
console.warn('App is running in debug mode');
}
Note: Runtime checks can be bypassed by determined attackers. They are more effective as a deterrent than a complete security solution.
5. Implementing Biometric Authentication
| Feature | Description | Implementation | Notes |
|---|---|---|---|
| LocalAuthentication | Native biometric authentication API | import { authenticate } from 'react-native-local-authentication'; |
Supports Face ID, Touch ID, and fingerprint |
| Authentication Types | Supported authentication methods | FaceID, TouchID, Fingerprint |
Available on iOS and Android devices with biometric sensors |
| Security Level | Level of security provided | Secure Hardware |
Authentication handled by secure hardware (TEE, HSM) |
Example: Using LocalAuthentication
import { authenticate } from 'react-native-local-authentication';
const handleBiometricAuth = async () => {
try {
const success = await authenticate({
promptMessage: 'Authenticate to access the app',
cancelMessage: 'Cancel',
fallbackMessage: 'Use passcode'
});
if (success) {
console.log('Authentication successful');
} else {
console.log('Authentication failed');
}
} catch (error) {
console.error('Error during authentication:', error);
}
};
Note: Ensure that biometric authentication is used in conjunction with secure storage for sensitive data.
Warning: Do not rely solely on biometric authentication for sensitive operations without a fallback mechanism.
| Platform | Support Status | Notes |
|---|---|---|
| iOS | NEW Supported | Face ID and Touch ID supported in iOS 11+ |
| Android | NEW Supported | Fingerprint and face recognition supported in Android 6+ |
6. Using Secure Communication
| Property | Description | Notes |
|---|---|---|
| HTTPS | Enforce secure communication over HTTPS | All network requests should use HTTPS to prevent man-in-the-middle attacks |
| TLS Version | Use TLS 1.2 or higher | Ensure the app enforces minimum TLS version support |
| Certificate Pinning | Implement certificate pinning for sensitive APIs | Prevents connection to servers with invalid certificates |
Example: Enforcing HTTPS in API calls
const apiCall = async () => {
const response = await fetch("https://api.example.com/data", {
method: "GET",
headers: {
"Content-Type": "application/json"
}
});
return response.json();
};
Example: Certificate Pinning Setup (iOS)
import Foundation
func urlSessionDidReceiveChallenge(
session: URLSession,
challenge: URLAuthenticationChallenge
) -> (URLSession.AuthChallengeDisposition, URL?) {
guard let serverTrust = challenge.protectionSpace.serverTrust else {
return (.cancelAuthenticationChallenge, nil)
}
let pinnedCertificates = [/* Add certificate hashes here */]
let isTrusted = pinnedCertificates.contains { certHash in
// Compare certificate hash with known good ones
return true // Placeholder logic
}
if isTrusted {
return (.useCredential, URLCredential(trust: serverTrust))
} else {
return (.cancelAuthenticationChallenge, nil)
}
}
Note: Certificate pinning should be used selectively for high-security endpoints and not applied globally to avoid breaking app functionality during certificate updates.
7. Validating User Input
| Input Type | Validation Method | Description | Example |
|---|---|---|---|
| Text Input | RegExp |
Validate text format using regex patterns | /^[A-Za-z0-9_]{3,20}$/ |
RegExp |
Validate email format using regex | /^[^\s@]+@[^\s@]+\.[^\s@]+$/ |
|
| Phone Number | IntlTelInput |
Validate phone numbers with country code | import 'intl-tel-input' |
| URL | URL Constructor |
Validate URL format using built-in constructor | new URL(input) |
Example: Email Validation
const validateEmail = (email) => {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
};
console.log(validateEmail('user@example.com')); // true
Example: Phone Number Validation
import intlTelInput from 'intl-tel-input';
const phoneInput = document.getElementById('phone');
intlTelInput(phoneInput, {
utilsScript: 'https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js'
});
Note: Always sanitize and validate user input on both client and server side to prevent injection attacks.
8. Implementing App Transport Security
| Property | Description | Default Value | Notes |
|---|---|---|---|
| NSAppTransportSecurity | Root key for ATS configuration in Info.plist | Not set | Required to enable ATS settings |
| NSAllowsArbitraryLoads | Allows loading of arbitrary content (not recommended) | false |
Deprecated in iOS 9+; use specific exceptions instead |
| NSAllowsArbitraryLoadsInWebContent | Allows arbitrary loads in web content (e.g., WKWebView) | false |
Useful for development or specific web content needs |
| NSAllowsLocalNetworking | Allows local networking (e.g., localhost) | false |
Useful for local development or testing |
| NSExceptionDomains | Defines exceptions for specific domains (e.g., staging servers) | Not set | Allows specific domains to bypass ATS restrictions |
Example: ATS Configuration in Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoadsInWebContent</key>
<false/>
<key>NSAllowsLocalNetworking</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>staging.example.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
</dict>
</plist>
Note: ATS is enabled by default in iOS 9+ and macOS 10.11+. It enforces secure connections by default, requiring HTTPS for all network requests.
Warning: Setting
NSAllowsArbitraryLoads to true disables ATS entirely and is not recommended for production apps.
ATS Best Practices
- Enable ATS by default for all network traffic
- Use
NSExceptionDomainsto allow specific domains that do not support HTTPS - Avoid using
NSAllowsArbitraryLoadsin production builds - Use secure HTTP (HTTPS) for all network requests
9. Preventing Screenshot Capture
| Method | Description | Notes |
|---|---|---|
| react-native-screenshot-prevention | import { preventScreenshot } from 'react-native-screenshot-prevention'; |
Library to disable screenshot capture on Android and iOS |
| StatusBar.setHidden | StatusBar.setHidden(true); |
Can help hide UI elements from screenshots |
Example: Using react-native-screenshot-prevention
import { preventScreenshot } from 'react-native-screenshot-prevention';
preventScreenshot(true); // Enable screenshot prevention
Note: Screenshot prevention is not 100% reliable and can be bypassed by determined users. It's more of a deterrent than a security feature.
10. Using ProGuard
Note: ProGuard is primarily used in Android development to obfuscate and optimize Java bytecode. For React Native apps targeting Android, ProGuard can be configured to help secure the app's native code.
| Configuration File | Description | Example |
|---|---|---|
| proguard-rules.pro | File containing ProGuard configuration rules | proguard-rules.pro |
Example: Basic ProGuard Configuration
-keep class com.example.reactnative.** { *; }
-keepclassmembers class com.example.reactnative.** {
<init>(android.content.Context, android.util.AttributeSet);
}
Example: Enabling ProGuard in Android Build
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
Key ProGuard Features for React Native
- Obfuscation - Renames classes and methods to make reverse engineering harder.
- Optimization - Removes unused code and reduces app size.
- Shrinking - Eliminates unused classes, fields, and methods.
Warning: Improper ProGuard configuration can break your React Native app. Ensure that any custom native modules or libraries are properly configured to work with ProGuard.
11. Implementing Root/Jailbreak Detection
Example: Basic Jailbreak Detection
import { Platform } from 'react-native';
const isJailbroken = () => {
// Check for common jailbreak indicators
if (Platform.OS === 'android') {
return (
// Check for root flags
process.execSync('ls /system/bin/su', { stdio: 'ignore' })
.status === 0
);
} else {
// For iOS, check for越狱 indicators
return (
process.execSync('ls /Applications/Blackra1n.app', { stdio: 'ignore' })
.status === 0
);
}
};
export { isJailbroken };
| Method | Description | Notes |
|---|---|---|
| isJailbroken() | Detects if the device is jailbroken/rooted | Uses system checks to identify compromised devices |
Note: Root/jailbreak detection is not foolproof and can be bypassed by determined attackers.