In AppSealing Blog

What was ideated during a Facebook Hackathon in 2013 and later launched as an open-source platform for the public in 2015, has now become one of the most widely used frameworks for app development. React Native, is based on JavaScript and allows developers to build natively rendered mobile apps for both iOS and Android operating systems. It uses React JavaScript (ReactJS), which is an open-source, front-end JavaScript library for building (user interface) UI interfaces and components for apps and websites. In this article, we discuss the aspects of react native security for apps built on the open-source mobile application framework.

React Native Security

React native, like all JavaScript-based frameworks, is vulnerable to security threats. An analysis from a react native protection standpoint needs to take into consideration different parts of the framework as well as the connection between them. Since the source code of the application is available to the client, front-end JS applications are inherently vulnerable to being modified or getting sensitive data easily breached. Some of the most common security issues in JS applications are:

Cross-site scripting:
This is also known as an XSS attack and occurs when an attacker tricks the website into running random JS code in the user’s browser. There are two kinds of XSS attacks: The reflected XSS attack, which happens when a link with some text information is processed by the browser as a code, and the stored XSS attack, where the attacker gains server access and any code run on the server can generate information on the client’s webpage.

Insecure randomness and links:
This happens when the links are based on data entered by the client, and an attacker adds malicious code to the original JS code. Clicking on the link then launches the attacker’s script on the browser.

Server-side rendering attacker-controlled initial state:
This happens if the app is being rendered on the server-side. Creation of a primary version of the page can also generate a document variable from the JSON string. This could be dangerous as any data provided to the JSON.stringify() function will be converted into a string which will then be seen on the page.

Arbitrary code execution (ACE):
It occurs when an attacker can execute arbitrary commands on the target process using a program called arbitrary code execution exploit. This can be extremely harmful as all users of the product will be exposed to the malware.

Zil slip:
This threat occurs when the security of the code library is compromised and the attacker can unzip malicious code or files outside the target directory. This would allow the attacker to even overwrite important system or configuration files.

Hence, protecting react native applications from malicious third parties is of primary concern when building an app. While React Native does not come with any in-built ways of storing sensitive information, there are some solutions that can be used to increase app security on both Android and iOS platforms. The sections below discuss some of them in detail.

Securing App-to-Server Connection

The communication between the client and the server on React Native needs to be secured, mainly because it is an open-source platform and is vulnerable to security threats. The most commonly used web services for communications between the client and the server is REST-based on HTTP. In an HTTPS connection, two things of prime importance are: a valid certificate presented by the server during handshaking and a cipher that is used for data encryption during transmission. The certificate serves as an identity proof of the server. Unless the server can provide a valid certificate signed by a trusted Certificate Authority (CA) present in the client, the connection will be aborted. This mechanism can be misused by attackers either by installing a malicious root CA certificate on user devices which would trick the client to trust the CA certificates signed by the attacker or by compromising a CA completely. It is important to note that different users with separate code variables should be assigned a realm attribute to avoid a mismatch in the authentication of the user credentials. Even a small mismatch between the server response mechanism and the realm attribute would compromise the security of the app and allow access to unauthorized users.

SSL Pinning in React Native

Secure sockets layer (SSL) is a protocol to establish authentic and encrypted links between networking computers. The transport layer security (TLS) protocol was an update to SSL in 1999 but they are often collectively referred to as “SSL” or “SSL/TLS”. SSL pinning refers to the technique that is used to validate the server certificates on the client side even after SSL handshaking. A list of trusted certificates is embedded in the client app during development so as to compare them with the server certificates during runtime. If the server certificates do not match with the local list of certificates, the connection will immediately be disrupted and no data will be sent to the server. This ensures that the clients are connecting only with the trusted servers. However, it should be noted that once the pinned certificate has expired, the future certificates must be pinned to the client applications before release. The updated certificate, if not pinned, will not be recognized by the client and any further communication will be terminated. This is known as ‘app bricking’. SSL Pinning can usually be done in two ways by either pinning the whole certificate or just its hashed key. The latter is the more desirable option as the same key can be used to sign the updated certificate and prevent instances of bricking. 

Certificate pinning can be implemented on Android in three different ways:


1. Network security configuration

This is a native method of pinning certificates on Android. This is a simple method and doesn’t need any coding. The only catch here is that it is supported only on Android N and above. It lets developers customize the security settings in a secure configuration file specific to a domain and app without having to modify the app code. The configuration uses an XML file which is created under the res\xml directory. The XML file can be declared by:

<?xml version=”1.0″ encoding=”utf-8″?>
<manifest … >
         … >

The network security file can now be configured using the tags <base-config> and <domain-config>.

  • The <base-config> declares configurations that need to be applied to the  whole app regardless of the domain that holds the connection.
  • The <domain-config> configures rules specific to domains of the developer’s choice.

2. TrustManager

The TrustManager decides if the credentials given by the peer will be allowed by the app. This technique from the can be used for certificate pinning through the following steps:

  • Add the certificate file, preferably in the PEM or DER format without comment lines, to the directory.
  • Initialise the KeyStore with the certificate

val resource_stream = resources.openRawResource(R.raw.cert)
val key_store_type = KeyStore.getDefaultType()
val key_store = KeyStore.getInstance(key_store_type)
key_store.load(resource_stream, null)

  • Create the TrustManager instance

val trust_manager_algorithm = TrustManagerFactory.getDefaultAlgorithm()
val trust_manager_factory = TrustManagerFactory.getInstance(trust_manager_algorithm)

  • The final step is to create the SSL context using the TLS protocol. A secure SSL connection is then created with the TrustManager.

val ssl_context = SSLContext.getInstance(“TLS”)
ssl_context.init(null, trust_manager_factory.trustManagers, null)
val url = URL(““)
val url_connection = url.openConnection() as HttpsURLConnection
url_connection.sslSocketFactory = ssl_context.socketFactory

3. OkHttp and Certificate Pinning

This is an easy to use and famous library from Square and is used by Retrofit for networking. To begin, a certificate pinning instance is created from the OkHttp CertificatePinner builder and a corresponding domain and fingerprint is added to it. The final step is to add the builder to the OkHttp client.

val certificatePinner = CertificatePinner.Builder()
val okHttpClient = OkHttpClient.Builder()

Multiple fingerprints can also be added to the builder if the current one is going to expire. The certificate files can also be imported to the resource folder as in the case of TrustManager.The next step is to write a class that will extract the fingerprint from the file. The fingerprint should be mentioned in the gradle file as a build-config field.

There are two ways of implementing SSL Pinning on React Native applications, either by using React Native libraries such as react-native-pinch or react-native-ssl-pinning or by using the native method. The implementation on the native platform is different for Android and iOS. The first step is to generate a certificate for the domain using the command openssl s_client -showcerts -servername <domain(i.e.> -connect <domain(i.e.>:443.

The output would be the entire certificate in the chain (leaf certificate at the top and root certificate at the bottom). 

For the iOS platform, TrustKit, which contains an inbuilt script (, is generally used for SSL Pinning. Just download the file and use the command $ python <your .pem file> to retrieve the public key hash.

A self-signed, leaf, intermediate, or root certificate can be added for SSL pinning. A leaf certificate completely ensures that the certificate belongs to the client and the connection is secure. The app needs to be updated to prevent losing connection with the server as the certificate has a very short expiry time. The use of back-up keys is highly recommended in the case of leaf certificates. An intermediate certificate is dependent on the intermediate certificate authority. The advantage of using this method is that any changes made to the leaf certificates are automatically updated on the app. However, one must use an intermediate certificate only if the provider can be trusted. A root certificate is dependent on all the intermediate certificates approved by the root certificate authority. This is a relatively unsafe method as any breach in the security of the intermediate certificates can make the app vulnerable to attacks by hackers.

React Native Storage Security

Developers often store persistent data inside the application. In React Native, there are several methods to do this like Async-storage, sqlite, pouchdb and realm. 

Here are some plugins which can add a security layer to the application:

  • SQLite: It is a popular and one of the most common ways of storing data. SQLCipher is a widely used open-source extension for the SQLite encryption. The cipher is encrypted by a 256 bit AES that cannot be accessed without a key. There are two libraries on React Native that provide the SQLCipher: react-native-sqlcipher-2 (can be used with pouchdb as an ORM provider) and react-native-sqlcipher-storage (based on the Cordova implementation).
  • Realm: It is a faster database compared to SQLite and supports encryption by default. It also uses the AES256 algorithm and the encryption is verified using SHA-2 HMAC hash.

Apart from these, React Native has some native methods to store sensitive data for both iOS and Android. The React Native libraries that provide secure storage are discussed below.

iOS: Keychain Services

Apple’s security frameworks offer keychain services that help programmers to securely store information. Using these services, apps can store small chunks of user data on an encrypted database called a keychain. A keychain primarily consists of two parts: an encrypted database (SecKeychain class) and items that have been inserted into the database (SecKeychainItem class). Sensitive information such as passwords, certificates, credit card details, tokens, etc., can be stored here. The react-native-keychain library provides keychain/keystore access to React Native applications.

The keychain can be easily installed from the React-Native-Keychain-Library by running the following command:
yarn add react-native-keychain

The code library can then be linked to the keychain with the command:
react-native link react-native-keychain

A successful linkage can be verified by checking if the library was added to the

The application can then be rebuild using:
react-native run-ios
react-native run-android

Upon the completion of the above steps, the react-native-keychain-library can be used within the app. setGenericPassword(username, password, [{ accessControl, accessible, accessGroup, service }]) stores the username/password combination in secure storage and resolves to true if there is an entry. It should also be noted that the function can only store string values. Credentials which have an object value have to be stored using JSON.stringify.

getGenericPassword([{ authenticationPrompt, service }]) retrieves the username/password combination from the secure storage. The function resolves to { username, password }if there is an entry and to false if not. In case there are any permission related errors, the function is rejected. Similar to setGenericPassword, this function too can only retrieve strings.Credentials in the object form can be accessed using JSON.parse

The username/password combination can be removed or reset from secure storage by using the resetGenericPassword function. 

The requestSharedWebCredentials() function is available for iOS users and lets them opt for a shared web credential. It requires an additional set-up on the app as well as the server. The function resolves to { server, username, password } if approved and to false if denied. In case the platform doesn’t support the function or there are no shared credentials, it displays an error message. 

setSharedWebCredentials(server, username, password) is used to set shared web credentials and the function resolves to true when it is approved. 

The setInternetCredentials(server, username, password, [{ accessControl, accessible, accessGroup }]) function is used to store the server name along with the username and password in secure storage. 

getInternetCredentials(server, [{ authenticationPrompt }]) retrieves the server/username/password from the secure storage.

resetInternetCredentials(server) function removes the server/username/password combination from the secure storage completely and resets it. 

The canImplyAuthentication([{ authenticationType }]) function is used to check if the authentication policy is supported on the device with the settings chosen by the user. It should be used in combination with the accessControl function available in the setter functions. The function resolves to true if approved.

getSupportedBiometryType() tells us about the hardware biometric support available in the device. It resolves to a Keychain.BIOMETRY_TYPE enum when supported, or else shows null. It works on both Android and iOS devices. The biometric types that are returned are :

TOUCH_ID (iOS only)
FACE_ID (iOS only)
FINGERPRINT (Android only)

The API also provides the Keychain.ACCESSIBLE enum to determine when the Keychain can be accessed. The user can choose an option based on the type of application and sensitivity of information. Some commonly used options are:

WHEN_UNLOCKED: The data in the Keychain can be accessed only when the device is unlocked.

ALWAYS: The Keychain is always accessible, which could be extremely unsafe. 

WHEN_UNLOCKED_THIS_DEVICE_ONLYThe Keychain can be accessed only when unlocked on a specific device.Items that are attributed to this enum cannot be migrated to another device. 

ALWAYS_THIS_DEVICE_ONLY: The Keychain is accessible only when it is on a specific device, regardless of whether the device is unlocked or not.

AFTER_FIRST_UNLOCK: The data in the Keychain cannot be accessed unless the device is unlocked after restarting it.

AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY: The Keychain is accessible only after the first time it was unlocked from a specific device. It cannot be accessed after a restart unless the device has been unlocked. This probably is the most secure option. Items with this attribute too do not migrate to a different device.

WHEN_PASSCODE_SET_THIS_DEVICE_ONLY: The data in the Keychain can be accessed only when the device is unlocked. The option is available only if there is a passcode set on the device.

The Keychain.ACCESS_CONTROL enum gives options to determine who has access to items in the Keychain. The following options are available to control access to Keychain items:

USER_PRESENCE The item can be accessed either with a Touch ID or passcode.
BIOMETRY_ANY There is a constraint to access an item with Touch ID only for any of the enrolled fingers.
BIOMETRY_CURRENT_SET The item can be accessed with Touch ID only for currently enrolled fingers.
DEVICE_PASSCODE An item can be accessed with a passcode.
APPLICATION_PASSWORD There is a constraint to use an application-provided password for the generation of data encrypted keys.
BIOMETRY_ANY_OR_DEVICE_PASSCODE The item can be accessed with Touch ID for any of the enrolled fingers or passcode.
BIOMETRY_CURRENT_SET_OR_DEVICE_PASSCODE There is a constraint to access an item with Touch ID only for the currently enrolled fingers or passcode. 


Android: Secure SharedPreferences

The Android equivalent for key-value data storage is called SharedPreferences. It provides a framework where persistent key-value primitive data types can be saved and retrieved. However, it is crucial to note that SharedPreferences stores data as plain text, and hence sensitive data must be encrypted to secure it from potential threats. While one can write their own encryption wrapper around SharedPreferences using the Android Keystore, the easier and safer option is to store the encrypted shared preferences on the AndroidX Security Library which has a rather simple and easy-to-use UI. The first step to getting started with the AndroidX Security Library is to add the dependency implementation”″ to the module-level build.gradle file. Since the library is currently in the alpha phase, it is necessary to keep in mind that parts of the API could be changed or removed in future versions. After adding the dependency an encryption master key has to be created and stored on the Android Keystore. This can be easily done by placing the following code where the EncryptedSharedPreferences instance is to be created:
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec).

A default specification, AES256_GCM_SPEC, is given to create the master key. It is recommended to use this specification but there also is the option to give KeyGenParameterSpec if one needs more control over the generation of the key. Lastly, an instance of EncryptedSharedPreferences, which is a wrapper around SharedPreferences and handles the encryption, is needed. While instances for SharedPreferences can directly be fetched from Context#getSharedPreferences or Activity#getPreferences, one needs to create their own instance of EncryptedSharedPreferences.

It can be easily done with:

val sharedPreferences = EncryptedSharedPreferences.create(

One needs to pass  a name to the SharedPreferences file,  the  masterKeyAlias that was created earlier, and the Context.The last two arguments encrypt the key and value respectively. The EncryptedSharedPreferences instance can be used exactly like SharedPreferences to read values. 

val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)
val sharedPreferences = EncryptedSharedPreferences.create(
// storing a value
   .putString(“some_key”, “some_data”)
// reading a value
sharedPreferences.getString(“some_key”, “some_default_value”) // -> “some_data”

Android Keystore

The Android Keystore lets developers store cryptographic keys to secure them from extraction from the device. It also offers mechanisms to control how and when the keys are used, such as asking for user authentication before a key can be used or restricting the usage of keys only in certain cryptographic modes. The following security measures are used to secure the Android Keystore keys from extraction:

  • The Key material does not enter the application process. This ensures that even in the event of the app’s process being compromised, the hacker will not be able to extract any key material.
  • The Key material is bound to secure hardware such as Trusted Execution Environment (TEE) or Secure Element (SE) of the device. This feature doesn’t let the key material to be exposed anywhere outside the secure hardware. Hence, even if an attacker is able to use any of the keys on Keystore, they cannot be extracted from the device. To check for this feature, obtain the KeyInfo for the key and look for the return value of  KeyInfo.isInsideSecurityHardware().

To prevent the unauthorized usage of keys on the Android device, the apps can specify the authorized uses while generating or importing the key. The authorizations cannot be changed once a key has been generated or imported. The key use authorizations supported on the Android Keystore can be categorized into the following:

  • Cryptography: Authorized key algorithm, block modes, padding schemes, operations or purposes (encrypt, decrypt, sign, verify) and digests where the key can be used.
  • Temporal validity interval: Specification of the time interval where the key is authorized for usage.
  • User authentication: The user has to be authenticated recently enough to be able to use the key.

On the Android OS, apps are required to be signed in for them to be installed. This can be done by creating a Keystore which is basically a storage system for security certificates. An APK needs to be signed using a public key certificate before deployment of the app to distribution services like the Google Play Store. Signing an APK ensures that any future updates to it for the same app are done by the developer and not a malicious third party. Before deploying an app one needs to consider the lifespan of the app since the same app cannot be deployed if it is signed by another key at any point in the future. Once the app’s lifespan has been decided, the Keystore can be generated using the tool keytool. keytool located in the Java JDK installation. keytool has a number of commands, the most common for Android apps being -genkeypair, also abbreviated as -genkey. The commonly used -genkey options are:


-alias               Keypair alias name
-keyalg               Algorithm used to generate keypair
-keysize               Keypair size (in bits)
-validity               Keypair validity duration ( in days)


For example, the command keytool -genkey -v -keystore release.keystore -alias example -keyalg RSA -keysize 2048 -validity 12000, results in a keystore file called release.keystore with an RSA-2048 public/private keypair by the alias name of example and a validity of 12,000 days. A strong password for both the keystore and the key is needed to sign an APK. An APK can be signed in the following two ways:

  • Signing with Gradle

To build a project on gradle, the developer can create an android.signingConfig and then associate it with one or more android.buildTypes. The simplest way to enter the keystore name and alias into the gradle build script:

android {
    signingConfigs {
        release {
            storeFile config.keystore
            storePassword storePassword
            keyAlias example
            keyPassword keyPassword
    buildTypes {
        release {
            signingConfig signingConfigs.release

  • Signing Manually 

To sign the APK manually, use apksigner which is  located at {ANDROID_SDK_DIRECTORY}/build-tools/{BUILD_TOOLS_VERSION}/apksigner for build-tools revision 24.0.3 or higher. This tool uses the public/private key pair stored in the keystore in order to generate a public key certificate. It then attaches the certificate to the APK where the private key gets associated in a unique way.  The APK is then zipaligned to ensure that the uncompressed data in the app starts at a predictable offset. Also, Google Play Store requires zipaligned APKs. 

zipalign -v -p 4 app-flavor-buildtype-unsigned.apk app-flavor-buildtype-unsigned-aligned.apk

  • After your APK is zipaligned, sign it using apksigner:

apksigner sign –ks release.keystore –out app-flavor-buildtype.apk app-flavor-buildtype-unsigned-aligned.apk

The command line then prompts to enter a password for the keystore. The password for the keystore and the key needs to be entered independently. The –ks-pass and –key-pass options can be used to be prompted for the passwords individually. Follow each option with stdin to capture the passwords at the command line. apksigner sign –ks example.keystore –ks-pass stdin –key-pass stdin –out app-signed.apk app.apk


Investigating React Native API Security Concerns


Application programming interface (API) is a dataset, mostly in the JSON format, with specific endpoints. Accessing data from the API would mean accessing the specific endpoints with the API framework. React APIs are used to establish communication between the application and other platforms and services. They also give us the option to control other devices or one specific device where the app is installed. These APIs can make the app vulnerable to security threats or man-in-the-middle (MITM) attacks if the authentication is not proper or there are flaws in the business logic. Cross-Site Scripting and SQL injection (SQLi) are also possible. In React Native, the APIs automatically document information to execute necessary commands internally. 

The security failures in API can be reduced or prevented in the following ways:

  • Validation of all API call commands with their respective API schemas
  • Periodic and timely validations of the schema to prevent any malicious injections or security attacks
  • Ensuring that the application is secure with SSL/TLS pinning

Securing React Native Against DDoS Attacks

Distributed denial of service (DDoS) is a kind of malicious attack which lets unauthorised users make certain application services inaccessible to the actual user. This vulnerability typically occurs when the IPs of the services are not masked properly or the application was not secure enough. These attacks stop the communication between the client and the server, thereby leading to a disruption in the online services of the app. It has also been seen that a DDoS attack could simply flood the React project with malicious traffic instead of causing a disruption in any existing service. Some commonly encountered security attacks because of DDoS are as follows:

  • UDP flooding – Causes inaccessibility of host services
  • ICMP flooding – Slows down the React application considerably
  • SYN flooding – Makes the application services vulnerable to easy exploitation
  • Ping of Death (POD) – Causes an overflow of memory buffers
  • HTTP flooding – Suspension of the online services which ultimately causes a complete shutdown of the application services.

Here are some ways in which the DDoS attacks can be dealt with:

  • Scrubbing the application both during the development and after it to identify any DDoS threats
  • Installation of visitor identification mechanisms to avoid any malicious users from accessing the program codes
  • Making calls on the server and never on the client side
  • Securing the web-app layers with the help of CAPTCHAs or JS tests
  • Rate-limiting the number of requests to an IP from the same source

Code Obfuscation

Code Obfuscation or minification is the most primary and initial method of storing sensitive data. It takes a legible code and makes it unreadable to the human eye with the help of softwares such as Uglify. As the Java codes on React Native are stored as a DEX file which is readable unless obfuscated. 

React Native also has an inbuilt library for obfuscation known as react-native-obfuscating-transformer that lets users implement obfuscation for JavaScript code as well as all of native. 

The first step is to activate obfuscation from the gradle file of the application. This can be done by navigating to [app_name]/android/app/build.gradle  inside the buildTypes section. The code below can then be used:

android {
    // … other config
    buildTypes {
        release {
            debuggable false
            shrinkResources true
            zipAlignEnabled true
            minifyEnabled true
            useProguard true
            setProguardFiles([getDefaultProguardFile(‘proguard-android.txt’), ‘’])
    // … other config

minifyEnabled and useProguard activate the minification and obfuscation of the code while the setProguardFiles directive indicates the location of the proguard configuration. Proguard, while securing the code, also performs additional optimisations which remove unnecessary parts and reduces the final app size. The proguard configuration can be set up by the ( file where all the exceptions that are needed to obfuscate the code will be added. 


Advanced React Native Security

There are some threats which are related to the network requests as apps run on multiple devices. The execution of apps on rooted and jailbroken devices should be completely avoided as they are inherently insecure by intent. Rooted devices allow attackers to overcome the OS security mechanisms and gain access to secured storage as well as spoof data. JailMonkey can be used to detect root or jailbreak in an application. It can also detect if mock locations can be set. SafetyNet, which is an 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, 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.


Runtime application self-protection (RASP) tools can continuously detect attacks on the applications’ storage and protect the app. The tool is built inside the app’s runtime environment and can analyze the performance and behavior of the app by controlling the app’s runtime execution. It confers an additional layer of security and works in tandem with the other security and app-monitoring tools. An app must control the app runtime execution, monitor app performance, and behavior and detect intrusions or abnormal behavior to qualify for the RASP category.


React Native is one of the most popular and efficient app-building frameworks. The cross-platform access, ease of use, cost and resource optimization as well as the excellent user interface make it a great choice for developers across the globe. The availability of ready-made components and in-built libraries make it suitable for achieving simple functionalities with ease. However, since the framework is based on JavaScript code, apps on the platform are vulnerable to rampant security threats and malicious third-party intrusions. The usage of similar components across apps also increases the risk of a security breach. Hence, developers should make use of all security features available on Android and iOS to ensure protection of their React Native applications. It is also important to create a threat model based on the use case and specifications of the app, and then take necessary precautions to prevent an attack on the app’s security. While there is no bullet-proof mechanism to ensure 100 percent security, with the integration of appropriate libraries and APIs, the incidence of risk can certainly be reduced.

To secure your applications without any additional coding, click on the link below to know more about AppSealing and sign-up for a free trial.

Secure My App

Govindraj Basatwar, Global Business Head
Govindraj Basatwar, Global Business Head
A Techo-Commerical evangelist who create, develop, and execute a clear vision for teams. Successfully created a SaaS business model with multi Million Dollar revenues globally. Proven leadership track record of establishing foreign companies in India with market entering strategy, business plan, sales, and business development activities.

Leave a Comment

Javascript Security