Application Security

Securing React Native Applications

August 12th, 2022 | By Jscrambler | 18 min read

Securing React Native applications allows us to minimize the security risks of these apps, which use a lot of third-party libraries.

React Native is a popular cross-platform JavaScript framework. Components of React Native apps render in a native UI. Discover the dedicated React Native page for security.

Are React Native apps secure?

React Native applications are secure if you regularly check the third-party libraries for vulnerabilities and provide robust client-side security solutions.

Explaining React Native

React Native has an alternative approach for cross-platform development. Traditionally, Cordova-based frameworks used WebView to render the whole application. In contrast, React Native applications run the JS code in a JavaScript VM based on JavaScript Core.

The application uses native JavaScript Core on iOS. On Android, JavaScript Core libraries are bundled in an APK. On newer versions, React Native runs on Hermes, and the Hermes engine is bundled in both Android and iOS apps.

The JavaScript Bridge handles communication between Native and JavaScript code. The source JS files are compiled into one bundle called an entry file.

In development mode, the application fetches the file. This file is bundled on a local server.

For production, the application logic is usually bundled in a single file, usually index.android.bundle or index.ios.bundle.

Similar to Cordova, the bundle file is present in the assets folder, and, as also happens with Cordova, we can assume React Native apps are containers that run JS code. Expo implements this functionality in its framework.

Under certain limitations, Expo can run different business logic in a single application. The entry file is the core application logic.

We will be dividing the article into the following sections:

  • Securing app-to-server connection

  • Securing local data

  • Advanced integrity checks

Securing App-to-Server Connection

Usually, smartphone apps communicate with the backend server via APIs. Insecure communication is highlighted in OWASP at #3 in the top 10:

Mobile applications frequently do not protect network traffic. They may use SSL or TLS for authentication. This inconsistency leads to the risk of exposing data and session IDs to interception. The use of transport security does not mean the app has implemented it correctly. To detect flaws, observe the phone's network traffic. More subtle flaws require inspecting the design of the application and the application's configuration.

Starting with iOS 9 and Android Pie, SSL is required by default. We can enable cleartext traffic, but it's not recommended. To secure the connection further, we can pin our server certificates.

SSL Pinning in React Native

Apps are dependent on Certificate Authorities (CA) and Domain Name Servers (DNS) to validate domains for TLS.

Unsafe certificates can be installed on a user device, opening the device to a Man-in-the-Middle attack. SSL pinning can be used to mitigate this risk.

We use the fetch API or libraries like Axios or Frisbee to consume APIs in our React Native applications. However, these libraries don't have support for SSL pinning. Let's explore the available plugins.

  • React-native-ssl-pinning: Uses OkHttp3 on Android and AFNetworking on iOS to provide SSL pinning and cookie handling.

    We will use fetch from the library to consume APIs. For this library, we will have to bundle the certificates inside the app. Necessary error handling needs to be implemented in older apps to handle certificate expiration. The app needs to be updated with newer certificates before they expire. This library uses promises and supports multi-part form data.

  • React-native-pinch: Similar to react-native-ssl-pinning. We have to bundle certificates inside the app. This library supports both promises and callbacks.


Alternatively, we can use native implementations as outlined by Javier Muñoz. He has implemented pinning for the Android and iOS versions natively.

Securing Local Storage

Quite often, we store data inside our application. There are multiple ways to store persistent data in React Native.

Async-storage, sqlite, pouchdb, and realm are some methods to store data. Insecure storage is highlighted at #2 in OWASP Mobile List:

Insecure data storage vulnerabilities occur when development teams assume that users or malware will not have access to a mobile device's filesystem and subsequent sensitive information stored on the device. Filesystems are easily accessible. Organizations should expect a malicious user or malware to inspect sensitive data stores. The use of poor encryption libraries is to be avoided. Rooting or jailbreaking a mobile device circumvents any encryption protections. When data is not protected properly, specialized tools are all that are needed to view application data.

Let's look at some plugins that store encrypted data in our apps. Also, we will be exploring some plugins that use native security features like Keychain and Keystore Access.

SQLite

SQLite is the most common way to store data. A trendy and open-source extension for SQLite encryption is SQLCipher.

Data in SQLCipher is encrypted via 256-bit AES, which can't be read without a key. React Native has two libraries that provide SQLCipher:

Realm

MongoDB Realm is a nice alternative database provider for React Native Apps.

It's much faster than SQLite, and it has support for encryption by default. It uses the AES256 algorithm, and the encrypted realm is verified using an SHA-2 HMAC hash.

To encrypt the data, we need to supply a 64-byte key while opening a realm. Find details about the library on GitHub.

Keychain and Keystore Access

Both iOS and Android have native techniques to store secure data.

Keychain services allow developers to store small chunks of data in an encrypted database. On Android, most plugins use the Android Keystore system for API 23 (Marshmallow) and above. For lower APIs, you can store encrypted data in shared preferences.

React Native has three libraries that provide secure storage along with biometric or face authentication:

  • React Native KeyChain: This plugin provides access to the Keychain and Keystore. It uses Keychain (iOS), Keystore (Android 23+), and Conceal. There is support for Biometric authentication.

    This plugin has multiple methods and options for both Android and iOS. However, it only allows the storage of the username and password.

  • React Native Sensitive Info: Similar to React Native Keychain, it uses Keychain (iOS) and shared preferences (Android) to store data. We can store multiple key-value pairs using this plugin.

  • React Native Encrypted Storage: This library is similar to React Native Sensitive. It uses EncryptedSharedPreferences on Android, which makes the library more secure on Android.

  • RN Secure Storage: This plugin is similar to React Native Sensitive Info. It uses Keychain (iOS), Keystore (Android 23+), and Secure Preferences to store data. We can store multiple key-value pairs.

Advanced Integrity Checks


JailMonkey and SafetyNet

Rooted and jailbroken devices should be considered insecure by intent. Root privileges allow users to circumvent OS security features, spoof data, analyze algorithms, and access secured storage. As a rule of thumb, the execution of the app on a rooted device should be avoided.

JailMonkey allows React Native applications to detect root or jailbreak. Apart from that, it can detect if mock locations can be set using developer tools.

SafetyNet is an Android-only API for detecting rooted devices and bootloader unlocks. React-native-google-safetynet is a wrapper plugin for SafetyNet's attestation API. It can be used to verify the user's device.

SafetyNet will be deprecated on June 30, 2024, and replaced by the Play Integrity API. But Applications have to migrate by June 30, 2023. If the apps are not migrated by June 30, 2023, the API will throw an error. SafetyNet will continue to work on older apps whose newer versions have migrated to Play Integrity in Production until 2024.

Additionally, we can use react-native-device-info to check if an app is running in an emulator.

Protecting the Application Logic


Earlier in the article, we mentioned how the application logic in the entry file is available in plain sight. In other words, a third party can retrieve the code, reverse-engineer sensitive logic, or even tamper with the code to abuse the app (such as unlocking features or violating license agreements).

Protecting the application logic is a recommendation in the OWASP Mobile Top 10. Specifically, the main concerns include code tampering:

Mobile code runs within an environment that is not under the control of the organization producing the code. At the same time, there are plenty of different ways of altering the environment in which that code runs. These changes allow an adversary to tinker with the code and modify it at will.

And reverse engineering:

Generally, most applications are susceptible to reverse engineering due to the inherent nature of code. Most languages used to write apps today are rich in metadata that greatly aides a programmer in debugging the app. This same capability also greatly aides an attacker in understanding how the app works.

Let’s highlight two different strategies to address this risk.

Hermes

Facebook introduced Hermes with the react-native 0.60.1 release.

Hermes is a new JavaScript Engine optimized for mobile apps. Hermes can be used in Android projects with react-native 0.60.4 by changing the enableHermes flag in build.gradle. Hermes can be used in iOS projects with React Native 0.64.

There is a writeup that analyses a React native application by fetching the index.android.bundle from an APK and using it in a basic <script> tag. This allows the attacker to sniff a key easily using Chrome and recon patterns.

This method doesn't work if we are using a Hermes.

Another way to obfuscate the file is to change the bundle asset name from index.android.bundle to secretname in build.gradle.

project.ext.react = [
    enableHermes: true,
    bundleAssetName: "secretname",  
]


Its key benefits are improved start-up time, decreased memory usage, and smaller app size. One of the strategies that Hermes uses to achieve this is precompiling JavaScript to bytecode. At first glance, this appears to make the entry file unreadable.

While Hermes introduces a certain degree of complexity to the entry-file code, it doesn’t obfuscate this code. An attacker may use an Android decompiler to reverse-engineer the bytecode and retrieve the application’s source code. Also, Hermes doesn’t do anything to prevent code tampering.

Hermes does allow a certain degree of obfuscation. To demonstrate the code, let's compile some code using Hermes. For the demo, I am using the native Hermes compiler to compile it to test.hbc. I am saving the snippet to test.js.

function startTime() {
    var today = new Date();
    var h = today.getHours();
    var m = today.getMinutes();
    var s = today.getSeconds();
    var key = "12345";
    m = checkTime(m);
    s = checkTime(s);
    document.getElementById('txt').innerHTML =
    h + ":" + m + ":" + s;
    var t = setTimeout(startTime, 500);
    importantFunction(key);
}
hermes -emit-binary -out test.hbc test.js


If we open the file using a Text Editor, we will see a lot of garbled data. If we see the file in a Hex Editor, we can find the name of the file and all functions used. The text can be found below.

12345:globalgetElementByIdocumentxtDatecheckTimegetHoursetTimeoutgetMinutestartTimegetSecondsimportantFunctioninnerHTMLprototype


Now I do know that importantFunction might have been used in test.hbc.

To analyze the code further we can use hbcdump utility. With hbcdump, we can disassemble test.hbc file to assembly code. We get the following output.

Bytecode File Information:
  Bytecode version number: 72
  Source hash: 1fee54304fb399317fce9f1c6697efcb922b008c
  Function count: 2
  String count: 16
  String Kind Entry count: 2
  RegExp count: 0
  CommonJS module offset: 0
  CommonJS module count: 0
  CommonJS module count (static): 0
  Bytecode options:
    staticBuiltins: 0
    cjsModulesStaticallyResolved: 0

Global String Table:
s0[ASCII, 0..4]: 12345
s1[ASCII, 5..5]: :
s2[ASCII, 6..11]: global
s3[ASCII, 32..34]: txt
i4[ASCII, 12..25] #04233820: getElementById
i5[ASCII, 25..32] #FDA9D117: document
i6[ASCII, 35..38] #CD347266: Date
i7[ASCII, 39..47] #0A0E7447: checkTime
i8[ASCII, 48..55] #A96453CC: getHours
i9[ASCII, 55..64] #500B50F7: setTimeout
i10[ASCII, 65..74] #B46B0210: getMinutes
i11[ASCII, 74..82] #1B6BC530: startTime
i12[ASCII, 83..92] #17C7B9B9: getSeconds
i13[ASCII, 93..109] #66135A46: importantFunction
i14[ASCII, 110..118] #DDBEE05B: innerHTML
i15[ASCII, 119..127] #807C5F3D: prototype

Function<global>0(1 params, 2 registers, 0 symbols):
Offset in debug table: src 0x0, vars 0x0
test.js[2:1]
    DeclareGlobalVar "startTime"
    CreateEnvironment r0
    CreateClosure r1, r0, 1
    GetGlobalObject r0
    PutById r0, r1, 1, "startTime"
    LoadConstUndefined r0
    Ret r0

Function<startTime>1(1 params, 17 registers, 0 symbols):
Offset in debug table: src 0x7, vars 0x0
test.js[2:22]
    GetGlobalObject r1
    TryGetById r0, r1, 1, "Date"
    GetByIdShort r2, r0, 2, "prototype"
    CreateThis r2, r2, r0
    Mov r10, r2
    Construct r0, r0, 1
    SelectObject r3, r2, r0
    GetByIdShort r0, r3, 3, "getHours"
    Call1 r2, r0, r3
    GetByIdShort r0, r3, 4, "getMinutes"
    Call1 r5, r0, r3
    GetByIdShort r0, r3, 5, "getSeconds"
    Call1 r4, r0, r3
    TryGetById r3, r1, 6, "checkTime"
    LoadConstUndefined r0
    Call2 r6, r3, r0, r5
    TryGetById r3, r1, 6, "checkTime"
    Call2 r4, r3, r0, r4
    TryGetById r7, r1, 7, "document"
    GetByIdShort r5, r7, 8, "getElementById"
    LoadConstString r3, "txt"
    Call2 r3, r5, r7, r3
    LoadConstString r5, ":"
    Add r2, r2, r5
    Add r2, r2, r6
    Add r2, r2, r5
    Add r2, r2, r4
    PutById r3, r2, 1, "innerHTML"
    TryGetById r4, r1, 9, "setTimeout"
    GetByIdShort r3, r1, 10, "startTime"
    LoadConstInt r2, 500
    Call3 r2, r4, r0, r3, r2
    TryGetById r2, r1, 11, "importantFunction"
    LoadConstString r1, "12345"
    Call2 r1, r2, r0, r1
    Ret r0

Debug filename table:
  0: test.js

Debug file table:
  Debug offset 0: string id 0

Debug data table:
  DebugOffset 0x0 for function at 0 starts at line=2, col=1 and emits locations for 14 (1 in total).
  DebugOffset 0x7 for function at 1 starts at line=2, col=22 and emits locations for 2 8 13 20 28 33 37 42 46 51 55 63 68 74 79 85 94 103 107 115 119 125 131 142 148 158 (26 in total).
  Debug table ends at debugOffset 0x59
Debug variables table:
  Offset: 0x0, vars count: 0, lexical parent: none

hbcdump> 


As an attacker, I am interested in the key variable, which I know is going to be passed to importantFunction. I can pretty much infer that the code is 12345. This is pretty much a simple scenario. But the point is that bytecode can be disassembled and inferences about the application can be made.

And this leads us to an approach that obfuscates React Native’s JavaScript source code to effectively mitigate the risk of code tampering and reverse engineering: Jscrambler.

Jscrambler: Protect Your JavaScript Code


Jscrambler provides a series of layers to protect JavaScript. Unlike most tools that only include (basic) obfuscation, Jscrambler provides three security layers:

  1. Polymorphic JavaScript & HTML5 obfuscation

  2. Code locks (domain, OS, browser, time frame);

  3. Self-defending (anti-tampering & anti-debugging);


By protecting the source code of React Native apps with Jscrambler, the resulting code is highly obfuscated, as can be observed in the example below:

// Original Code Example
function startTime() {
    var today = new Date();
    var h = today.getHours();
    var m = today.getMinutes();
    var s = today.getSeconds();
    m = checkTime(m);
    s = checkTime(s);
    document.getElementById('txt').innerHTML =
    h + ":" + m + ":" + s;
    var t = setTimeout(startTime, 500);
}
 
// Code Protected with Jscrambler (scroll right)
B3dd(f3dd());P5LL.t1WW=t1WW;h5UU(n5UU());P5LL.c3Y=function(){var W3Y=2;for(;W3Y!==1;){switch(W3Y){case 2:return{g3:function(V3){var O3Y=2;for(;O3Y!==10;){switch(O3Y){case 2:var F3=function(l7){var h3Y=2;for(;h3Y!==13;){switch(h3Y){case 1:var j3=0;h3Y=5;break;case 5:h3Y=j3<l7.length?4:9;break;case 2:var E3=[];h3Y=1;break;case 9:var P7,K3;h3Y=8;break;case 8:P7=E3.L9UU(function(){var k3Y=2;for(;k3Y!==1;){switch(k3Y){case 2:return 0.5-Y9UU.M9UU();break;}}}).y9UU('');K3=P5LL[P7];h3Y=6;break;case 4:E3.q9UU(R9UU.C9UU(l7[j3]+52));h3Y=3;break;case 3:j3++;h3Y=5;break;case 14:return K3;break;case 6:h3Y=!K3?8:14;break;}}};O3Y=1;break;case 5:var x3=0,D3=0;O3Y=4;break;case 1:var J3='',U3=k9UU(F3([64,-3,35,35])());O3Y=5;break;case 3:O3Y=D3===V3.length?9:8;break;case 8:J3+=R9UU.C9UU(U3.a9UU(x3)^V3.a9UU(D3));O3Y=7;break;case 7:x3++,D3++;O3Y=4;break;case 9:D3=0;O3Y=8;break;case 6:J3=J3.G9UU('=');var u3=0;var C3=function(I7){var l3Y=2;for(;l3Y!==20;){switch(l3Y){case 11:J3.O9UU.V9UU(J3,J3.p9UU(-10,10).p9UU(0,8));l3Y=5;break;case 13:J3.O9UU.V9UU(J3,J3.p9UU(-6,6).p9UU(0,4));l3Y=5;break;case 1:J3.O9UU.V9UU(J3,J3.p9UU(-5,5).p9UU(0,3));l3Y=5;break;case 10:C3=W3;l3Y=5;break;case 9:l3Y=u3===2&&I7===4?8:7;break;case 8:J3.O9UU.V9UU(J3,J3.p9UU(-4,4).p9UU(0,2));l3Y=5;break;case 7:l3Y=u3===3&&I7===5?6:14;break;case 6:J3.O9UU.V9UU(J3,J3.p9UU(-5,5).p9UU(0,3));l3Y=5;break;case 12:l3Y=u3===5&&I7===4?11:10;break;case 3:J3.O9UU.V9UU(J3,J3.p9UU(-8,8).p9UU(0,7));l3Y=5;break;case 5:return u3++,J3[I7];break;case 14:l3Y=u3===4&&I7===3?13:12;break;case 4:l3Y=u3===1&&I7===8?3:9;break;case 2:l3Y=u3===0&&I7===0?1:4;break;}}};var W3=function(S7){var u3Y=2;for(;u3Y!==1;){switch(u3Y){case 2:return J3[S7];break;}}};return C3;break;case 4:O3Y=x3<U3.length?3:6;break;}}}('NKP88I')};break;}}}();P5LL.D3Y=function (){return typeof P5LL.c3Y.g3==='function'?P5LL.c3Y.g3.apply(P5LL.c3Y,arguments):P5LL.c3Y.g3;};P5LL.Y3Y=function (){return typeof P5LL.c3Y.g3==='function'?P5LL.c3Y.g3.apply(P5LL.c3Y,arguments):P5LL.c3Y.g3;};P5LL.l1S=function (){return typeof P5LL.u1S.H1S==='function'?P5LL.u1S.H1S.apply(P5LL.u1S,arguments):P5LL.u1S.H1S;};P5LL.s7=function (){return typeof P5LL.V7.M3==='function'?P5LL.V7.M3.apply(P5LL.V7,arguments):P5LL.V7.M3;};function B3dd(){function N6(){var F7=2;for(;F7!==5;){switch(F7){case 2:var y7=[arguments];try{var Z7=2;for(;Z7!==9;){switch(Z7){case 2:y7[7]={};y7[5]=(1,y7[0][1])(y7[0][0]);y7[4]=[y7[5],y7[5].prototype][y7[0][3]];y7[7].value=y7[4][y7[0][2]];Z7=3;break;case 3:try{y7[0][0].Object.defineProperty(y7[4],y7[0][4],y7[7]);}catch(z6){y7[4][y7[0][4]]=y7[7].value;}Z7=9;break;}}}catch(H6){}F7=5;break;}}}var k7=2;for(;k7!==72;){switch(k7){case 59:O6[26]=O6[8];O6[26]+=O6[36];O6[26]+=O6[98];k7=56;break;case 11:O6[1]="";O6[1]="stract";O6[6]="C";O6[3]="b";O6[76]="ual";O6[60]="";O6[60]="esid";k7=15;break;case 6:O6[4]="__opti";O6[2]="F";O6[9]="";O6[9]="j";k7=11;break;case 50:O6[62]=O6[70];O6[62]+=O6[3];O6[62]+=O6[1];O6[32]=O6[9];k7=46;break;case 46:O6[32]+=O6[34];O6[32]+=O6[25];O6[80]=O6[4];O6[80]+=O6[5];k7=63;break;case 41:O6[51]+=O6[25];O6[40]=O6[93];O6[40]+=O6[36];O6[40]+=O6[98];O6[92]=O6[21];O6[92]+=O6[60];k7=54;break;case 2:var O6=[arguments];O6[7]="";O6[7]="ze";O6[5]="";k7=3;break;case 54:O6[92]+=O6[76];O6[24]=O6[6];O6[24]+=O6[34];O6[24]+=O6[25];k7=50;break;case 73:r2(a2,"apply",O6[14],O6[51]);k7=72;break;case 74:r2(o2,O6[92],O6[10],O6[40]);k7=73;break;case 77:r2(G6,"push",O6[14],O6[17]);k7=76;break;case 75:r2(o2,O6[62],O6[10],O6[24]);k7=74;break;case 24:O6[98]="d";O6[36]="";O6[36]="";O6[36]="3d";k7=35;break;case 28:O6[14]=1;O6[10]=0;O6[51]=O6[23];O6[51]+=O6[34];k7=41;break;case 55:r2(O2,"test",O6[14],O6[26]);k7=77;break;case 35:O6[25]="";O6[25]="";O6[25]="dd";O6[93]="x";O6[14]=6;O6[34]="3";O6[23]="D";k7=28;break;case 76:r2(o2,O6[80],O6[10],O6[32]);k7=75;break;case 15:O6[21]="";O6[70]="__a";O6[21]="__r";O6[98]="";k7=24;break;case 56:var r2=function(){var v7=2;for(;v7!==5;){switch(v7){case 2:var T6=[arguments];N6(O6[0][0],T6[0][0],T6[0][1],T6[0][2],T6[0][3]);v7=5;break;}}};k7=55;break;case 63:O6[80]+=O6[7];O6[17]=O6[2];O6[17]+=O6[34];O6[17]+=O6[25];k7=59;break;case 3:O6[5]="";O6[5]="mi";O6[4]="";O6[8]="E";k7=6;break;}}function O2(){var L7=2;for(;L7!==5;){switch(L7){case 2:var G7=[arguments];return G7[0][0].RegExp;break;}}}function G6(){var b7=2;for(;b7!==5;){switch(b7){case 2:var M7=[arguments];return M7[0][0].Array;break;}}}function a2(){var w7=2;for(;w7!==5;){switch(w7){case 2:var N7=[arguments];return N7[0][0].Function;break;}}}function o2(){var U7=2;for(;U7!==5;){switch(U7){case 2:var R7=[arguments];return R7[0][0];break;}}}}function P5LL(){}P5LL.f7=function (){return typeof P5LL.V7.M3==='function'?P5LL.V7.M3.apply(P5LL.V7,arguments):P5LL.V7.M3;};P5LL.D2o=function (){return typeof P5LL.H2o.T7o==='function'?P5LL.H2o.T7o.apply(P5LL.H2o,arguments):P5LL.H2o.T7o;};function h5UU(){function r7(){var q3Y=2;for(;q3Y!==5;){switch(q3Y){case 2:var o3Y=[arguments];return o3Y[0][0];break;}}}function c7(){var r3Y=2;for(;r3Y!==5;){switch(r3Y){case 2:var x8Y=[arguments];return x8Y[0][0].Function;break;}}}var L3Y=2;for(;L3Y!==79;){switch(L3Y){case 66:E7(r7,"String",A8Y[63],A8Y[91]);L3Y=90;break;case 55:A8Y[14]+=A8Y[19];A8Y[53]=A8Y[7];A8Y[53]+=A8Y[96];A8Y[53]+=A8Y[19];A8Y[91]=A8Y[2];A8Y[91]+=A8Y[96];L3Y=72;break;case 62:A8Y[13]+=A8Y[6];A8Y[13]+=A8Y[15];A8Y[39]=A8Y[5];A8Y[39]+=A8Y[96];A8Y[39]+=A8Y[19];A8Y[14]=A8Y[9];A8Y[14]+=A8Y[96];L3Y=55;break;case 25:A8Y[51]="V9";A8Y[19]="";A8Y[19]="";A8Y[19]="UU";L3Y=21;break;case 83:E7(T7,"split",A8Y[37],A8Y[89]);L3Y=82;break;case 82:E7(o7,"unshift",A8Y[37],A8Y[32]);L3Y=81;break;case 21:A8Y[96]="";A8Y[96]="9";A8Y[22]="";A8Y[22]="p";A8Y[37]=7;A8Y[37]=9;L3Y=30;break;case 84:E7(T7,"charCodeAt",A8Y[37],A8Y[50]);L3Y=83;break;case 6:A8Y[2]="R";A8Y[5]="Y";A8Y[8]="k";A8Y[1]="";L3Y=11;break;case 68:var E7=function(){var K3Y=2;for(;K3Y!==5;){switch(K3Y){case 2:var N8Y=[arguments];e7(A8Y[0][0],N8Y[0][0],N8Y[0][1],N8Y[0][2],N8Y[0][3]);K3Y=5;break;}}};L3Y=67;break;case 45:A8Y[73]=A8Y[3];A8Y[73]+=A8Y[6];A8Y[73]+=A8Y[15];A8Y[13]=A8Y[70];L3Y=62;break;case 72:A8Y[91]+=A8Y[19];A8Y[99]=A8Y[4];A8Y[99]+=A8Y[6];A8Y[99]+=A8Y[15];L3Y=68;break;case 16:A8Y[15]="";A8Y[77]="O";A8Y[15]="";A8Y[15]="U";L3Y=25;break;case 85:E7(r7,"decodeURI",A8Y[63],A8Y[21]);L3Y=84;break;case 2:var A8Y=[arguments];A8Y[4]="";A8Y[4]="q";A8Y[7]="";L3Y=3;break;case 42:A8Y[31]+=A8Y[19];A8Y[34]=A8Y[51];A8Y[34]+=A8Y[15];A8Y[34]+=A8Y[15];L3Y=38;break;case 67:E7(o7,"push",A8Y[37],A8Y[99]);L3Y=66;break;case 53:A8Y[89]+=A8Y[96];A8Y[89]+=A8Y[19];A8Y[50]=A8Y[1];A8Y[50]+=A8Y[15];L3Y=49;break;case 80:E7(o7,"splice",A8Y[37],A8Y[31]);L3Y=79;break;case 89:E7(o7,"sort",A8Y[37],A8Y[14]);L3Y=88;break;case 88:E7(r7,"Math",A8Y[63],A8Y[39]);L3Y=87;break;case 49:A8Y[50]+=A8Y[15];A8Y[21]=A8Y[8];A8Y[21]+=A8Y[96];A8Y[21]+=A8Y[19];L3Y=45;break;case 87:E7(O7,"random",A8Y[63],A8Y[13]);L3Y=86;break;case 38:A8Y[32]=A8Y[77];A8Y[32]+=A8Y[96];A8Y[32]+=A8Y[19];A8Y[89]=A8Y[82];L3Y=53;break;case 86:E7(o7,"join",A8Y[37],A8Y[73]);L3Y=85;break;case 90:E7(p7,"fromCharCode",A8Y[63],A8Y[53]);L3Y=89;break;case 3:A8Y[7]="C";A8Y[9]="";A8Y[9]="L";A8Y[5]="";L3Y=6;break;case 30:A8Y[37]=1;A8Y[63]=1;A8Y[63]=0;A8Y[31]=A8Y[22];A8Y[31]+=A8Y[96];L3Y=42;break;case 11:A8Y[6]="9U";A8Y[1]="a9";A8Y[3]="y";A8Y[70]="M";A8Y[82]="";A8Y[82]="G";L3Y=16;break;case 81:E7(c7,"apply",A8Y[37],A8Y[34]);L3Y=80;break;}}function O7(){var t3Y=2;for(;t3Y!==5;){switch(t3Y){case 2:var i8Y=[arguments];return i8Y[0][0].Math;break;}}}function o7(){var Q3Y=2;for(;Q3Y!==5;){switch(Q3Y){case 2:var d3Y=[arguments];return d3Y[0][0].Array;break;}}}function p7(){var a3Y=2;for(;a3Y!==5;){switch(a3Y){case 2:var E3Y=[arguments];return E3Y[0][0].String;break;}}}function T7(){var T3Y=2;for(;T3Y!==5;){switch(T3Y){case 2:var C3Y=[arguments];return C3Y[0][0].String;break;}}}function e7(){var I3Y=2;for(;I3Y!==5;){switch(I3Y){case 2:var j3Y=[arguments];I3Y=1;break;case 1:try{var G3Y=2;for(;G3Y!==9;){switch(G3Y){case 2:j3Y[4]={};j3Y[3]=(1,j3Y[0][1])(j3Y[0][0]);j3Y[6]=[j3Y[3],j3Y[3].prototype][j3Y[0][3]];j3Y[4].value=j3Y[6][j3Y[0][2]];try{j3Y[0][0].Object.defineProperty(j3Y[6],j3Y[0][4],j3Y[4]);}catch(T8Y){j3Y[6][j3Y[0][4]]=j3Y[4].value;}G3Y=9;break;}}}catch(I8Y){}I3Y=5;break;}}}}


On top of this obfuscation, we have the Self-Defending layer, which provides anti-debugging and anti-tampering capabilities and enables setting countermeasures like breaking the application, deleting cookies, or destroying the attacker’s environment.

To get started with protecting React Native source code with Jscrambler, check out the official guide.

Final Thoughts


This article provides an overview of techniques to harden a React Native application.

Developer surveys show that React Native is still a framework of choice, even among the development teams of large enterprises.

It’s then crucial to create a threat model and, depending on the application’s use case, employ the required measures to ensure that the application is properly secured.

Feel free to test how Jscrambler protects your React Native source code.

Jscrambler

The leader in client-side Web security. With Jscrambler, JavaScript applications become self-defensive and capable of detecting and blocking client-side attacks like Magecart.

View All Articles

Must read next

Web Development

Extended Guide to SafetyNet

SafetyNet enables you to protect your app against tampering, fake users, and more. With this tutorial, you'll learn how to set up SafetyNet in your own app.

April 17, 2019 | By Karan Gandhi | 7 min read

Web Security Javascript

Protecting JavaScript Source Code Using Obfuscation - Facts and Fiction

This will be the very first post in this blog. JScrambler exists since 2010 and it was about time we have a blog.

August 5, 2013 | By Jscrambler | 1 min read

Section Divider

Subscribe to Our Newsletter