Secure Your React Native app

Authors
Adam Trzciński
Software Engineer
@
Callstack

That was the case with our latest client, a fast-growing European bank which decided it’s about time and go React Native. Our React Native development company was tasked with building a mobile banking app from scratch, including a GraphQL server that would support it. And apart from delightful UX, one of the biggest challenges we faced was indeed securing the app and our customers.

Logging In

Usually, when logging a user in, we receive some kind of an access_token, which we use later on with all our network requests. Even better if this token expires — we do not want it to be valid forever, it’s not a very safe approach. (And if it expires, we have a refresh_token, that will, well, refresh our token, so user won’t be logged out from the app in the middle of a transaction). That’s the basic, but what can be done apart from this when logging users in? 

As an extra layer of security, we decided not to send the password the users type as a plaintext. Instead, we came up with a solution to XOR the password with an <rte-code>ENCRYPTION_KEY<rte-code> stored in device’s memory.

export const encryptXOR = (text: string, key: string) => {
const bytes = utf8.encode(text);
const output = [];
for (let i = 0; i < bytes.length; i++) {
output[i] = String.fromCharCode(
bytes[i].charCodeAt(0) ^ key[i % key.length].charCodeAt(0)
);
}
return base64.encode(output.join(''));
};

Example for XOR-ing a string with secret key

This way any third party person listening and monitoring network traffic will have no use of this encrypted string (unless they gains the knowledge about the key as well). The same key is kept securely on the server. When necessary, the backend can XOR the received string again, revealing the real passphrase, as XOR operation is reversible.

Secure Storage

Ok, that was for starters, what can we do more, to make life harder for any malicious people? I’ve mentioned access_token and refresh_token pair already, there are also some other keys that we have to persist on the device to use the app. And they are vulnerable for attacks as well, so we have to find a way to make them as secure as it gets!

The best solution for now is the one implemented by a manufacturer of the devices  —  Keychain on iOS devices and Keystore/SharedPreferences for Android. Here you can find a detailed explanation of how to use a react-native-keychain library and store key-value pairs in Secure Storage.

As an extra, (because we are paranoid) we salt each stored value with a unique key for 256-bit AES that we generate on the first launch of the app and which is unique for a device user have. This way, even if someone gets access to device memory (or a plaintext keychain data through jailbreak or root), they will be presented with hashes and not real data.

const encrypt = (text: string) => {
const textBytes = aes.utils.utf8.toBytes(text);
const aesCtr = new aes.ModeOfOperation.ctr(aes256key);
const encryptedBytes = aesCtr.encrypt(textBytes);
const encryptedHex = aes.utils.hex.fromBytes(encryptedBytes);
return encryptedHex;
};

‍Encrypting SecureStorage item

let aes256key: ?Array<number>;
sha256([brand, deviceId, ...otherIds].join('')).then(
(hash: string) => {
aes256key = Array.from(
Array.from(hash)
.map((c, i) => (i % 2 ? c : null))
.filter(Boolean)
.map(c => c.charCodeAt(0))
);
}
);

Creation of a AES256 key

export async function setItem(key: string, value: string) {
try {
return Keychain.setGenericPassword(key, encrypt(value), {
service: `${BUNDLE_ID}.${key}`,
});
} catch (error) {
Logger.warn(error.message);
return null;
}
}

‍And storing the item in SecureStorage

Obfuscation

Generating hashes solely on the device means nothing if someone really clever gets access to our code! If this happens, they will know how we generate all those AES keys, where we store them and all our hard work will go to waste. But actually, obfuscating our code is not as hard as one would fear. We can use react-native-obfuscating-transformer (it seems like there’s a lib for everything in React Native), and with a little-to-no effort to implement obfuscation for JavaScript code and for all native as well! There’s also a great StackOverflow answer that can help you with achieving this goal.

SSL pinning

Okay. We’ve ciphered our password (FYI, this can be also done for other login methods as well, e.g. Biometrics), hidden our secrets in a SecureStorage and obscured the code. It’s time to take care of network traffic and hide as much as possible from any potential spoofers.

Detailed explanation what is the certificate and public key pinning and why should we use it can be found at the OWASP website. For brevity sakes let’s say that with this approach we can eliminate so-called man-in-the-middle attack risks and hide our network requests that are made by an app. Aaaaand, yes! There’s also a lib for that’s called react-native-ssl-pinning. 🙂

Note, that this needs to be implemented on both sides of the communication chain, so the backend service needs to pin the certs as well.

let options = {};
if (!__DEV__) {
options = {
key: fs.readFileSync('./ssl/key.pem', 'utf8'),
cert: fs.readFileSync('./ssl/cert.pem', 'utf8'),
};
}
const server = __DEV__ ? app : https.createServer(options, app);
server.listen(PORT);

RASP

And lastly, there’s also protection from infected devices that users can have. Unfortunately, there’s not very much we as JS developers can do with this kind of issue (yet), but we can use trusted third parties to help us protect our app even further. A great example would be Runtime Application Self-Protection (RASP) provided by OneSpan (formerly Vasco). 

In a nutshell, it wraps our whole application with their own code, which detects any malicious software that can be present in users device and either blocks all unwanted interactions (like reading users input with screen readers), or shutting down the app completely if the threat is severe and there’s a risk of user’s data being compromised. 

Word of a warning though — while all other solutions are open-source and free, here we entrust our code and safety to an external entity (which can be viewed as a security risk on its own). It’s also obviously paid 😉

Closing words

Whenever we work with our users’ sensitive data, securing our apps should be a top priority. Security is a hard and vast topic. What’s worse, it’s easy to get wrong when implementing solutions ourselves. It’s crucial to lean on security experts and use established, battle-tested solutions. 

Thankfully, there are open source libraries perfectly suited for React Native apps, that we can use today, without too much hassle

That’s it for today, don’t forget to read our other blog posts, and keep on hacking! Creating a mobile app is not only about navigation and animations (however important they are). One of the main concerns when developing a mobile app is data security. Especially when this data is very sensitive and any security breach can lead to irreversible damage.  

Insights

Learn more about Security

cover

Dec 17Podcast

A (Secure) Christmas Carol: The Story of Npmezer Scrooge

A holiday story about npm audits, unpinned dependencies, and the real cost of ignoring JavaScript security warnings.

cover

Aug 20Article

Secure Communication Between Isolated React Native Instances

react-native-sandbox now supports secure, direct communication between sandboxed React Native instances, modeled after the web’s postMessage API with origin-based access control.

cover

May 13Article

React Native Legal: Zero-Config OSS License Screens for Your App

Add license acknowledgments to your React Native app with ease using React Native Legal-a zero-config tool for OSS compliance across iOS, Android, tvOS, and AndroidTV platforms.

cover

Jul 3Podcast

Authorization: Security Deep-Dive With Okta (Part 2)

Let's talk access control, delegated authorization, and more. Learn modern security frameworks and get tips for creating reliable apps.

cover

Jul 2Podcast

Authentication: Security Deep-Dive With Okta (Part 1)

Join us as we discuss authentication's past, present, and future. From MFA to passkeys, learn how to enhance security and efficiency in your applications.

cover

Mar 8Article

Learn Once, Hack Everywhere

Demystify reverse engineering of mobile apps, dive into tools, analyze .apk files, and secure React Native code with our article.

cover

Feb 21Article

SSL Pinning in React Native Apps

Learn to implement SSL pinning to future-proof your React Native app against security attacks. Dig deeper into native SSL implementations for iOS & Android.

cover

Oct 28Article

Stripe Identity: ID Authentication Made Simple

Learn about preventing fraud with Stripe Identity React Native SDK.

cover

Mar 19Tutorial

The Hard Way vs. The React Native AI SDK Way: Stop Writing Custom Modules for Every Model

Learn how React Native AI uses a unified provider model to switch between cloud and on-device LLMs without rewriting application logic, enabling reliable AI experiences online and offline.

cover

Mar 12Tutorial

Build Smarter Apps: Tool Calling & AI Orchestration Explained

Learn how on-device models in React Native can call tools, interact with external APIs, and return structured outputs you can use directly in application logic.

cover

Mar 5Tutorial

MLC LLM + React Native: On-Device AI Without the Pain

Learn how to run third-party on-device LLMs in React Native using MLC LLM. Choose your models, run them efficiently across platforms, and keep a unified JavaScript API with React Native AI.

cover

Feb 24Tutorial

Intelligent Fallbacks with Apple Intelligence in React Native

Learn how to use Apple’s built-in Foundation Models in React Native apps, reduce memory usage, and enable fast, fully on-device AI with React Native AI Apple.

cover

Feb 10Tutorial

What Is the React Native AI SDK? A Complete Intro & Quickstart

Learn why on-device AI matters for React Native apps, how local LLMs behave offline, and what problems React Native AI is designed to solve from day one.

cover

Jan 29Tutorial

Implementing an Android TurboModule with Kotlin

Learn how to implement a React Native TurboModule on Android, from the TypeScript spec and Codegen to the Kotlin implementation and package registration.

cover

Jan 19Tutorial

Working with Different Threads in Swift TurboModules

Learn how JavaScript, native module, background, and main threads interact in React Native TurboModules, and how to use GCD queues to run async work safely in Swift.

cover

Jan 9Tutorial

How to Add Type-Safe Constants to Swift TurboModules

Learn how to expose typed, immutable constants from a Swift TurboModule using Codegen, and get full end-to-end type safety between native and JavaScript.

cover

Dec 30Tutorial

Adding Event Emitters to Your TurboModule in Swift

Learn how to emit native events from a Swift TurboModule and subscribe to them in JavaScript, including proper memory management and Codegen integration.

cover

Dec 22Tutorial

Writing Your First TurboModule in Swift

Learn how to build a fully working TurboModule in Swift, integrate it with Codegen, and bridge it through Objective‑C inside an Expo app.