Let’s go banking
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.
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.
Encrypting SecureStorage item
Creation of a AES256 key
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.
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.