Securing React Native Application
Tags: react, reactnative, security, javascript
Published: 2021-07-12 13:57:30

Discover a ways to develop secure react native application.

**Key Points -**
* Screenshot Prevention
* Rooted/ Jailbroken Device Detection
* SSL Pinning
* Storage of Sensitive Data - API EndPoint / FB / Google / Firebase Keys
* Local Storage
* Deep Linking
* Android Specific Security
* iOS Specific Security
* Authentication Methods
* Data Encryption

###1. Screenshot Prevention
**iOS Integration -**

Screenshot Restriction is not possible on Straight Forward Way, But you can do something like, When application InActive - add Blur Layer/ View on window and when Active - remove BlurLayer/View.

Add Following Lines of Code in AppDelegate.m

```
// AppDelegate.m
- (void)applicationWillResignActive:(UIApplication *)application {
    UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
    UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
    [blurEffectView setFrame:self.window.bounds];
    blurEffectView.tag = 1234;
    blurEffectView.alpha = 0;
    [self.window addSubview:blurEffectView];
    [self.window bringSubviewToFront:blurEffectView];
    [UIView animateWithDuration:0.5 animations:^{
        blurEffectView.alpha = 1;
    }];
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    UIVisualEffectView *blurEffectView = [self.window viewWithTag:1234];
    [UIView animateWithDuration:0.5 animations:^{
      blurEffectView.alpha = 0;
    } completion:^(BOOL finished) {
        [blurEffectView removeFromSuperview];
    }];
}
```

**Android Integration -**

In Android it’s quite simple to restrict user to prevent taking screenshot - Go to MainActivity.java

```
// MainActivity.java
// Import Following 
+ import android.view.WindowManager;
+ import android.os.Bundle

Add following lines of Code
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+   super.onCreate(savedInstanceState);
+
+   getWindow().setFlags(
+     WindowManager.LayoutParams.FLAG_SECURE,
+     WindowManager.LayoutParams.FLAG_SECURE
+   );
+ }
```

Another way to prevent for screenshot - Go to MainActivity.java, In onCreate method add lines marked as +.

```
// MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    + getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}
```

###2. Rooted/ Jailbroken Device Detection
**iOS Integration -**

To detect iOS devices is Jailbroken, use following code. You need to create React Native Native Module and integrated iOS Code in iOS Project.

We will check following to detect Jailbroken devices -
* Check if Cydia is installed
* Check if the app can edit system files
* Check if the system contains suspicious files
* Check if other suspicious apps (FakeCarrier, Icy, etc.) is installed
* Check if Cydia is installed with alternative names (using URIScheme)

```
import Foundation
import UIKit

extension UIDevice {
    var isSimulator: Bool {
        return TARGET_OS_SIMULATOR != 0
    }
    
    var isJailBroken: Bool {
        get {
            if UIDevice.current.isSimulator { return false }
            if JailBrokenHelper.hasCydiaInstalled() { return true }
            if JailBrokenHelper.isContainsSuspiciousApps() { return true }
            if JailBrokenHelper.isSuspiciousSystemPathsExists() { return true }
            return JailBrokenHelper.canEditSystemFiles()
        }
    }
}

private struct JailBrokenHelper {
    //check if cydia is installed (using URI Scheme)
    static func hasCydiaInstalled() -> Bool {
        return UIApplication.shared.canOpenURL(URL(string: "cydia://")!)
    }
    
    //Check if suspicious apps (Cydia, FakeCarrier, Icy etc.) is installed
    static func isContainsSuspiciousApps() -> Bool {
        for path in suspiciousAppsPathToCheck {
            if FileManager.default.fileExists(atPath: path) {
                return true
            }
        }
        return false
    }
    
    //Check if system contains suspicious files
    static func isSuspiciousSystemPathsExists() -> Bool {
        for path in suspiciousSystemPathsToCheck {
            if FileManager.default.fileExists(atPath: path) {
                return true
            }
        }
        return false
    }
    
    //Check if app can edit system files
    static func canEditSystemFiles() -> Bool {
        let jailBreakText = "Developer Insider"
        do {
            try jailBreakText.write(toFile: jailBreakText, atomically: true, encoding: .utf8)
            return true
        } catch {
            return false
        }
    }
    
    //suspicious apps path to check
    static var suspiciousAppsPathToCheck: [String] {
        return ["/Applications/Cydia.app",
                "/Applications/blackra1n.app",
                "/Applications/FakeCarrier.app",
                "/Applications/Icy.app",
                "/Applications/IntelliScreen.app",
                "/Applications/MxTube.app",
                "/Applications/RockApp.app",
                "/Applications/SBSettings.app",
                "/Applications/WinterBoard.app"
        ]
    }
    
    //suspicious system paths to check
    static var suspiciousSystemPathsToCheck: [String] {
        return ["/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist",
                "/Library/MobileSubstrate/DynamicLibraries/Veency.plist",
                "/private/var/lib/apt",
                "/private/var/lib/apt/",
                "/private/var/lib/cydia",
                "/private/var/mobile/Library/SBSettings/Themes",
                "/private/var/stash",
                "/private/var/tmp/cydia.log",
                "/System/Library/LaunchDaemons/com.ikey.bbot.plist",
                "/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist",
                "/usr/bin/sshd",
                "/usr/libexec/sftp-server",
                "/usr/sbin/sshd",
                "/etc/apt",
                "/bin/bash",
                "/Library/MobileSubstrate/MobileSubstrate.dylib"
        ]
    }
}
```

Also, don't forget to add "Cydia" in LSApplicationQueriesSchemes key of info.plist. Otherwise canOpenURL will always return false.

```
<key>LSApplicationQueriesSchemes</key>
<array>
    <string>cydia</string>
</array>
```

**Android Integration -**
[Rootbear](https://github.com/scottyab/rootbeer) library helps in detection of rooted devices. Simply follow steps of installation and using React Native Native Module you can access Rootbear functions to detection of rooted devices.

Library will check following to detection of Rooted Devices

* checkRootManagementApps
* checkPotentiallyDangerousApps
* checkRootCloakingApps
* checkTestKeys
* checkForDangerousProps
* checkForBusyBoxBinary
* checkForSuBinary
* checkSuExists
* checkForRWSystem

It's simple to integrate in Code -

```
RootBeer rootBeer = new RootBeer(context);
if (rootBeer.isRooted()) {
    //we found indication of root
} else {
    //we didn't find indication of root
}
```

**SafetyNet** Android-only API that helps in the detection of rooted devices and bootloader unlocks. It also provides security against security threats, device tampering, malicious apps, and fake users.

The [react-native-google-safetynet](https://www.npmjs.com/package/react-native-google-safetynet), which is a wrapper plugin for SafetyNet’s API, can also be used to verify the device of the user. The react-native-device-info plugin can be used to see if the app is being run on an emulator.

###3. SSL Pinning
SLL Pinning can be done using 3 different ways
* Public Key Pinning
* Certificate Pinning
* Subject Public Key Info (SPKI) Pinning

**iOS Integration -** Place your .cer files in your iOS Project. Don't forget to add them in your Build Phases - Copy Bundle Resources, in Xcode

**Android Integration -** Place your .cer files under src/main/assets/

**Certificate Pinning With react-native-ssl-pinning -**
iOS - drag .cer to Xcode project, mark your target and "Copy items if needed"

```
fetch(url, {
  method: "POST" ,
  timeoutInterval: communication_timeout, // milliseconds
  body: body,
  // your certificates array (needed only in android) ios will pick it automatically
  sslPinning: {
    certs: ["cert1","cert2"] // your certificates name (without extension), for example cert1.cer, cert2.cer
  },
  headers: {
    Accept: "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*", "e_platform": "mobile",
  }
})
.then(response => {
  console.log("response received:", response);
})
.catch(err => {
  console.log("error:", err);
})
```

**Public Key Pinning With react-native-ssl-pinning -**
**iOS -** drag .cer to Xcode project, mark your target and "Copy items if needed". No Extra steps needed for public key pinning. AFNetworking will extract public key directly from certificate.

**Android -** public key should be extracted by following command, simply replace google with your domain.

```
openssl s_client -servername google.com -connect google.com:443 | openssl x509 -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
```

```
import {fetch} from 'react-native-ssl-pinning';

fetch("https://publicobject.com", {
  method: "GET" ,
  timeoutInterval: 10000, // milliseconds
  // your certificates array (needed only in android) ios will pick it automatically
  pkPinning: true,
  sslPinning: {
    certs: [
      "sha256//r8udi/Mxd6pLOS73Djkex2EP4nFnIWXCqeHsTDRqy8=",
    ]
  },
  headers: {
    Accept: "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*", "e_platform": "mobile",
  }
})
```

**Certificate Pinning With react-native-pinch -**
```
import pinch from 'react-native-pinch';

pinch.fetch('https://my-api.com/v1/endpoint', {
  method: 'post',
  headers: { customHeader: 'customValue' },
  body: '{"firstName": "Jake", "lastName": "Moxey"}',
  timeoutInterval: 10000 // timeout after 10 seconds
  sslPinning: {
    cert: 'cert-file-name', // cert file name without the .cer
    certs: ['cert-file-name-1', 'cert-file-name-2'], // optionally specify multiple certificates
  }
})
  .then(res => console.log("response -", res);)
  .catch(err => console.log("error -",err);)
```

###4. Storage of Sensitive Data - API EndPoint / FB / Google / Firebase Keys

Never store your API EndPoint, AccessKey, Firebase, Google/FB Social Key directly into Code. Your bundle can be decoded into plaintext and all information can be extracted.

It’s recommended to use [react-native-config](https://github.com/luggit/react-native-config/) and [react-native-dot-env](https://github.com/goatandsheep/react-native-dotenv) to place your secure key, endpoint.

**Note:** react-native-config module doesn’t encrypt secrets for packaging, so do not store sensitive keys in .env file.

###5. Local Storage

Developer often needs to store data locally, sometime developer prefer asyncstorage to store accesskey/ access-token/ user token. But AsyncStorage is un-encrypted storage, so information can be extract from AsyncStorage.

React Native does not provide solution for secure data storage. There are pre-existing solution in iOS & Android we all know about iOS Keychain and Android Keystore.

[iOS Keychain -](https://developer.apple.com/documentation/security/keychain_services)

In iOS we use Keychain Services which allows developers to securely store sensitive information like certificates, tokens, securekeys, passwords, and any other sensitive information like secrets of open platform services which we are using in our application.

[Android Keystore -](https://developer.android.com/training/articles/keystore)
The Android Keystore allows developers to store cryptographic keys in a container to make it more difficult to extract from the device. Once keys are in the keystore, they can be used for cryptographic operations with the key material remaining non-exportable.

To use iOS Keychain & Android Secure Shared Preferences from React Native, you can use following NPM.

* react-native-encrypted-storage
* react-native-keychain
* redux-persist-sensitive-storage

**Note:** redux-persist-sensitive-storage uses react-native-sensitive-info with redux-persist. react-native-sensitive-info manages all data stored in Android Shared Preferences and iOS Keychain. Android Shared Preferences are not secure, but there is a branch of react-native-sensitive-info that uses the Android key-store instead of shared preferences. You can use that branch with redux-persist-sensitive-storage if you prefer.

###6. Deep Linking
Deep linking is a way to open application from other sources. Deep Link contains textual data along with Link. Like **yourappname://**

Let's say you have E-Commerce application and your deep link is yourappname://products/1 means it will open your app with detail of product 1.

Deep links are not secure and you should not append any sensitive information in deep link.

**Security issues while dealing with deep linking -**
There is no centralised method of registering URL schemes. As developer, you can use any URL scheme you choose by configuring it in Xcode for iOS or adding an intent on Android.

Malicious app can hijack your data by also **using the same scheme** and then obtaining access to the data your link contains. Sending something like **yourappname://products/1** is not harmful, but sending tokens is a security concern.

iOS allows one single URL Scheme to be claimed by multiple apps. For instance, sample:// can be used by two completely separate apps in their implementation of URL Schemes. This is how some malicious apps can take advantage of the URL Scheme and compromise users.

**Security solutions to overcome deep linking security issue -**
Apple introduced Universal Links in iOS 9 as a solution to the lack of graceful fallback functionality in custom URI scheme deep links. Universal Links are standard web links that point to both a web page and a piece of content inside an app.

When a Universal Link is opened, iOS checks to see if any installed app is registered for that domain. If so, the app is launched immediately without ever loading the web page. If not, the web URL (which can be a simple redirect to the App Store) is loaded in Safari.

Setting up a universal link (HTTP or HTTPS) login interface, and musing a random identifier to authenticate the received login token locally, prevents hijacking and malicious login token replaying.

###7. Android Specific Security
Let’s see how to protect our APK or app bundle from reverse engineering attacks.

Hackers can easily access our Codebase by doing reverse engineering with APK or app bundle file. To avoid it we can add Pro Guard rules. Pro Guard obfuscates your code. So if someone reverse engineer it, it’s not readable and saves you from engineering attacks. Pro Guard also used to reduces APK size by removing unused code and resources. If your project contain any third party library then you can add the Pro Guard rules of that library in your rules file.

To enable Pro Guard rule we have to enable the minifyEnabled property in app/build.gradle file.

```
buildTypes {
    release: {
       minifyEnabled true
    }
 }
```

###8. iOS Specific Security
Let’s see how we can restrict the insecure domains usage in iOS. It will save us from transport layer attacks. You can restrict insecure domains by configuring some properties within your Info.plist file.

Now, let’s see what you should add in your Info.plist file for that.

From iOS 9.0 Apple has introduced NSAppTransportSecurity which you can find inside info.plist file. Inside **NSAppTransportSecurity** there is one key NSAllowArbitraryLoads which is set to NO by default that means you are agreed with security benefits. In some cases while you are working with localhost or with HTTP domain if required then you have to make it YES otherwise you can’t make network request with those insecure domains.

There are chances that your app might get rejected while uploading it to apple store because you set NSAllowArbitraryLoads value as YES. To overcome that you can use NSExceptionDomains by providing a list of domains inside that. Application will consider like you have agreed to all over security benefits excepts the domain those domains which you have mentioned in NSExceptionDomains (although you have set NSAllowArbitraryLoads value as YES).

```
<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <true/>
  <key>NSExceptionDomains</key>
  <dict>
    <key>localhost</key>
    <dict>
      <key>NSExceptionAllowsInsecureHTTPLoads</key>
      <true/>
    </dict>
  </dict>
</dict>
```

###9. Authentication Methods

Nowadays OAuth has become more popular for Authentication between one application interacting with another application. Consider a case where your application communicates with an API to send/retrieve data from the server. How do servers know the coming request is authenticated? OAuth 2.0 makes the authentication process simple for you. Instead of sharing passwords OAuth allows authentication using Token. It's an approach to use [JWT Token](https://jwt.io/) for API Authentication.

###10. Data Encryption
[Crypto JS](https://www.npmjs.com/package/crypto-js) is popular Javascript library for crypto standards. To store, send data to server it's approched to Encrypt data using CrytpJS. So, it's is not redable by enable directly.

**Thanks for reading Blog!**

KPITENG | DIGITAL TRANSFORMATION
www.kpiteng.com/blogs | hello@kpiteng.com
Connect | Follow Us On - [Linkedin](https://www.linkedin.com/company/kpiteng/) | [Facebook](https://www.facebook.com/kpiteng) | [Instagram] (https://www.instagram.com/kpiteng)