Skip to main content

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-info or 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.
// Example using obfuscator.io
var obfuscator = require('javascript-obfuscator');
var obfuscatedCode = obfuscator.obfuscate(originalCode, {
    compact: true,
    controlFlowFlattening: true,
    controlFlowFlatteningThreshold: 1,
    deadCodeInjection: true,
    deadCodeInjectionThreshold: 1,
    debugProtection: false,
    debugProtectionInterval: 0,
    disableConsoleOutput: true,
    identifierNamesGenerator: 'hexadecimal',
    renameGlobals: false,
    rotateStringArray: true,
    stringArray: true,
    stringArrayThreshold: 1,
    stringArrayIndexShift: true,
    stringArrayRotate: true,
    stringArrayShuffle: true,
    stringArrayWrappersCount: 2,
    stringArrayWrappersType: 'hexadecimal',
    stringArrayWrappersStartIndex: 0,
    stringArrayWrappersParametersMaxCount: 2,
    stringArrayWrappersParametersMinCount: 1,
    stringArrayWrappersStartIndex: 0,
    stringArrayWrappersParametersMaxCount: 2,
    stringArrayWrappersParametersMinCount: 1,
    unicodeEscapeSequence: true
}).obfuscatedCode;
Use tools like JavaScript Obfuscator to obfuscate your code before deployment.
Native Code Obfuscation Protects native modules and code by obfuscating them.
// Example using ProGuard (Android)
-keep class com.example.MyClass {
    public void myMethod();
}
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.
// Example in iOS (Objective-C)
- (void)secureMethod {
    // Implementation logic here
}
Use React Native with native modules for sensitive logic.
Runtime Checks Verify app integrity at runtime to detect tampering or debugging.
// Example in JavaScript
if (typeof __DEV__ !== 'undefined' && __DEV__) {
    // Handle development mode
}
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}$/
Email 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 NSExceptionDomains to allow specific domains that do not support HTTPS
  • Avoid using NSAllowsArbitraryLoads in 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(&#x27;ls /system/bin/su&#x27;, { stdio: &#x27;ignore&#x27; })
      .status === 0
    );
  } else {
    // For iOS, check for越狱 indicators
    return (
      process.execSync(&#x27;ls /Applications/Blackra1n.app&#x27;, { stdio: &#x27;ignore&#x27; })
      .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.

Was this article helpful?