The demand for feature-rich and user-friendly applications has skyrocketed in today's mobile-driven world. React Native has emerged as a popular framework for developing cross-platform mobile apps, allowing developers to create high-performance applications using JavaScript. However, with the increasing use of mobile apps comes the responsibility of ensuring their security. In this blog post, we will explore various topics related to the security of React Native and mobile apps, providing you with best practices and strategies to safeguard your application and protect your users' data. By implementing these security measures, you can build robust and trustworthy apps that instil confidence in your users and maintain the integrity of your application.

Looking for help securing your React Native app? Contact the Morrow team today.

Let’s start by grouping them by category.

Data at Rest

When talking about "Data at Rest," we refer to the data an app uses and stores locally. This can range from simple, small chunks of data to entire local databases (offline-first approach).

Typically, developers use AsyncStorage to store simple key-value pairs. Although the storage of a specific project is not accessible from other apps, it remains in plaintext (unencrypted) and could potentially be accessed by third parties. Therefore, it is not recommended that sensitive data be stored in AsyncStorage.

We can store encrypted values using the expo-secure-store library, which functions similarly to AsyncStorage but with added security.

For storing large amounts of data, we may also use SQLite local databases like expo-sqlite. However, these solutions are also non-encrypted storage. If we need to store sensitive data, it's crucial to use databases that support encryption, such as SQLite Encryption Extension or RealmDB.

When using an encrypted database, the app must have and provide the encryption key, which needs to persist across sessions. In such cases, it is essential to store the encryption key safely using libraries like expo-secure-store or react-native-keychain.

Remember, protecting our users' data is of utmost importance, so always consider the sensitivity of the information we’re storing and choose the appropriate storage method accordingly.

Disabling Android App Auto Backup

Android's App Auto Backup feature may seem convenient, but it could potentially compromise the security of your app's data. This feature automatically backs up app and user data to the user's Google Drive account. While this backed-up data is stored in a "hidden" area of Google Drive, it still means that sensitive information from your app is being exported outside of the device.

Moreover, when a user uninstalls and then reinstalls your app, the backed-up data is automatically restored. This could lead to a serious security breach, where a new user installing the app gains access to the data of a previous user who had uninstalled the app.

To prevent such security risks, it is highly recommended that you disable the Android App Auto Backup feature for your app. By doing so, we ensure that your app's data remains solely on the user's device and is not inadvertently shared with others.

Prioritising the security and privacy of your users' data is crucial in maintaining the trust and integrity of your app. Take the necessary steps to disable Android App Auto Backup and safeguard your users' information.

Data In Transit

"Data in Transit" refers to the process of data moving between two points, such as from a client to a server or between two servers. It is the data that is actively moving across a network, either through the internet or a private network.

When data is in transit, it is vulnerable to interception, tampering, or eavesdropping by unauthorised parties. Therefore, it is crucial to secure data in transit.

HTTPS/TLS

When it comes to securing data in transit, using HTTPS (Hypertext Transfer Protocol Secure) or TLS (Transport Layer Security) is essential. These protocols encrypt the communication between the client and the server, ensuring that sensitive information remains confidential and protected from eavesdropping or tampering.

So, we have to make sure that our application and any third-party libraries that use communication protocols have enabled encryption (HTTPS/TLS) according to industry standards.

SSL PINNING

SSL pinning, also known as certificate pinning, is a security technique used to prevent man-in-the-middle attacks and ensure that an app communicates only with the intended server. It involves hardcoding the expected SSL/TLS certificate or public key information directly into the app's code rather than relying solely on the device's trust store.

When an app with SSL pinning initiates a secure connection, it compares the server's certificate with the pinned certificate or public key information. If there is a mismatch, the connection is immediately terminated, protecting the app from potential security breaches.

By implementing SSL pinning, you can add an extra layer of security to your app's communication channels, ensuring that even if an attacker manages to compromise a certificate authority or obtain a fraudulent certificate, your app will refuse to connect to the malicious server.

However, it's important to note that SSL pinning requires careful management and updates, as pinned certificates need to be refreshed periodically to avoid potential disruptions in app functionality.

There are various React Native libraries available like https://github.com/MaxToyberman/react-native-ssl-pinning

Enhancing Security with End-to-End Encryption

While it may seem like overkill for typical applications, implementing end-to-end encryption can provide an additional layer of protection for data in transit. This approach involves encrypting the data itself before sending it to the server, using a key that is known only to the client and the server.

By adding this extra layer of encryption, you can ensure that even if a successful man-in-the-middle attack decodes the traffic at the HTTPS/SSL layer, the attacker will still be unable to access the sensitive information within the encrypted data payload.

Implementing end-to-end encryption adds a robust level of security to your app's communication channels, safeguarding your users' data from unauthorised access and interception. It provides peace of mind knowing that even in the event of a breach at the network level, your app's data remains secure and confidential.

While end-to-end encryption may require additional development effort, the enhanced security it offers makes it a worthwhile consideration for apps handling sensitive user information.

Regularly Rotating and Updating API Keys

API keys play a vital role in authenticating and authorising access to your app's backend services. However, using the same API keys for an extended period can pose serious security risks. If an attacker manages to obtain your API keys, they could gain unauthorised access to your app's data and functionality.

To mitigate this risk, it is crucial to rotate and update your API keys regularly. By periodically generating new API keys and revoking old ones, you can limit the window of opportunity for attackers to exploit compromised keys.

Implementing a robust key rotation policy ensures that even if a key is compromised, its potential impact is minimised. Set a schedule to rotate your API keys at regular intervals, such as every few months or after a specific number of requests.

Sending Sensitive Data Securely: Choosing POST Over GET

When it comes to transmitting sensitive information between your app and the server, it's essential to choose the appropriate HTTP method. While GET requests are commonly used for retrieving data, they have a significant drawback when it comes to security. GET requests to append the parameters, including sensitive information, directly to the URL.

This means that if you send sensitive data using a GET request, it will be visible in the URL, potentially exposing it to various security risks. URLs are often logged by servers, proxies, and network equipment, making them vulnerable to interception and unauthorised access.

POST requests should always be used when sending sensitive data to mitigate this risk. POST requests include the parameters in the request body rather than appending them to the URL. This ensures that sensitive information is not explicitly visible in the URL and is less likely to be inadvertently logged or exposed.

Remote Logging

All applications have remote logging, where we log information, insights, or crash events. We should take extra care not to include any sensitive information or at least obscured information in those logs. These are stored in servers, and although we must take extra care of who has access to them, we still need to consider them compromisable.

Authentication

Authentication is a critical component of app security, ensuring that only authorised users can access the app's features and data. Implementing a robust authentication mechanism is essential to protect user accounts, prevent unauthorized access, and maintain the integrity of your app.

When opting for the traditional user/password authentication method, it's crucial to prioritise security measures to protect user accounts. Here are some key considerations:

Password Complexity Requirements

Enforce strong password policies that mandate specific criteria, such as a minimum length, a mix of uppercase and lowercase letters, numbers, and special characters.

Implement password strength meters or indicators to provide visual feedback and encourage users to create robust passwords.

Consider implementing password blacklists to prevent the use of commonly used or compromised passwords.

Secure Password Storage

Never store passwords locally on the user's device.

Always transmit passwords securely over encrypted channels, such as HTTPS with TLS.

OAuth2 and OpenID Connect: Secure Authentication and Authorization Standards

OAuth2 (OAuth 2.0) and OpenID Connect (OIDC) are widely adopted industry standards for secure authentication and authorisation in modern applications.

OAuth2 is an authorisation framework that enables third-party applications to obtain limited access to user accounts on an HTTP service, such as Facebook or Google, without exposing the user's credentials. It provides a secure way for users to grant specific permissions to applications, allowing them to access protected resources on their behalf.

OAuth2 defines several authorisation grant types, including the Authorization Code Grant, Implicit Grant, Resource Owner Password Credentials Grant, and Client Credentials Grant. These grant types cater to different scenarios and provide flexibility in implementing authentication and authorisation flows.

OpenID Connect (OIDC) is an authentication layer built on top of OAuth2. It extends OAuth2 by adding an identity token, which contains claims about the authenticated user. OIDC allows applications to verify the user's identity and obtain basic profile information in a standardised manner.

OIDC introduces concepts like ID tokens, which are JSON Web Tokens (JWTs) that contain user profile information and user info endpoints, which provide additional details about the authenticated user. It also defines standard scopes, such as "openid," "profile," and "email," to request specific sets of user attributes.

These allow storing only authentication tokens locally and not the password itself to authenticate requests with the servers.

Redirect protection with “Pixy” (PKCE)

OAuth2 and OIDC, which are popular authentication methods, rely on redirection. The user authenticates through a third-party provider and is then redirected back to the app with an authorisation code. This code can be exchanged for a JWT token, which serves as the authentication token. 

While the redirection process is secure on the web, mobile apps and React Native face a risk where a malicious app could register the same deep linking scheme and trick users into triggering it, potentially stealing authentication data. To prevent this, an additional security measure called PKCE ("Pixy") can be implemented. PKCE ensures that the authentication and token exchange requests originate from the same client. 

More information on this topic can be found in the React Native documentation: https://reactnative.dev/docs/security#oauth2-and-redirects

Clear User Data on Logout

When a user logs out of your app, it's crucial to handle their locally stored data securely. If the app stores user information on the device, special care must be taken to ensure that the data is either removed or protected upon logout. Since there's no guarantee that the same user will log back in during the next session, failing to clear or safeguard the data could potentially expose sensitive information to unauthorised access. To maintain user privacy and data security, implement proper logout mechanisms that thoroughly clear user-specific data from local storage or encrypt it using secure methods.

Least Privilege Principle

The Least Privilege Principle is a fundamental concept in-app security that aims to minimise the risk of unauthorised access and data breaches. It states that users should be granted only the minimum set of privileges and permissions necessary to perform their intended tasks within the app. This principle applies to data access and device permissions, such as access to the camera, location, or storage.

Strong Authentication Methods

To enhance security, consider implementing stronger authentication methods based on your app's requirements. Biometric authentication, such as fingerprint scanning or facial recognition (FaceID), provides an additional layer of protection. Alternatively, you can employ One-Time-Password (OTP) authentication using solutions like Google Authenticator, which generates time-based, single-use codes. Another option is Magic Link or Email Link authentication, where users receive a unique, temporary link via email to log in securely. By incorporating these methods, you can significantly reduce the risk of unauthorised access to user accounts.

Software and Package Updating

Maintaining the security of your React Native app requires ongoing effort beyond the initial development phase. As new vulnerabilities and threats arise, it's essential to stay proactive and address risks through regular software updates and vulnerability protection.

Update Packages Regularly

Keeping your app's packages and dependencies up to date is crucial for maintaining security. Vulnerabilities and flaws may be discovered in your packages, exposing your app to attacks. By regularly updating to the latest package versions, you benefit from bug fixes, security patches, and improvements that address known vulnerabilities, strengthening your app's defence against evolving threats.

Choose your packages wisely

When selecting packages for your React Native app, making informed choices is important. While small, lightweight packages may seem appealing, consider the long-term implications. Opt for packages from well-known and trusted authors with a proven track record of maintenance. These authors are likelier to adhere to industry best practices, follow security standards, and provide ongoing support and updates.

Relying on obscure or unmaintained packages can introduce risks, as they may contain unpatched vulnerabilities or lack necessary security measures. If a package becomes unmaintained, you may be stuck with outdated dependencies that are difficult to update or replace, leaving your app vulnerable. Investing in packages from reputable sources and actively maintained projects ensures the long-term security and stability of your React Native app.

Audit Your Packages

Conducting regular audits of your app's packages is essential to identify vulnerabilities. Utilise tools like npm audit to detect potential security risks. When vulnerabilities are found, updating the affected packages to their latest versions is a top priority. By being proactive and keeping your dependencies current, you significantly reduce the risk of your app succumbing to known security threats.

Force Users to Update

When a vulnerability is discovered and patched in your application or its packages, having a mechanism to ensure users update promptly is crucial. React Native and Expo SDK offer Over-The-Air (OTA) updates, enabling you to deliver new application versions with security fixes quickly.

However, for critical vulnerabilities requiring an update at the native level, implementing a "forcing" mechanism is recommended. This can involve using a flag or an API endpoint that the application checks, preventing users from proceeding until they manually update to the latest version. Such measures safeguard users against known security risks.

Data Validation

Validating the data that enters your application is crucial, especially when sending it to your application servers. Data can originate from various sources, including direct user input, local storage, file systems, and third-party network services. It's essential to validate all data and reject any unexpected information coming from these sources to maintain the integrity and security of your application.

Screen protection

Protecting sensitive information displayed on the screen is vital to ensure user privacy and prevent unauthorised access. Just like hiding passwords in forms, the same principle should be applied to other sensitive data, such as bank account information and PINs. By obscuring or masking sensitive information, you protect users from "shoulder-surfing" attacks, where a malicious individual might physically spy on a victim's screen to obtain confidential data. Implementing screen protection measures, such as hiding sensitive fields or using visual obfuscation techniques, enhances the security of your application and safeguards user information from prying eyes.

Application Protection

It's important to recognise that code for mobile applications, particularly React Native applications, is always present on the user's device. Although it may not be as easily accessible as web code, it is still bundled within the application. This means that a malicious user can potentially obtain, analyse, and extract secrets from the code. While we can't completely prevent this, we can take measures to make the code as difficult as possible to analyse and understand.

Minification

Expo CLI automatically minifies the code during the production bundling process. In addition to reducing the size of the code bundle, minification makes the code harder to read and comprehend. This adds an extra layer of protection against reverse engineering attempts.

Obfuscation 

Obfuscation takes code protection a step further by making the code even more challenging to understand and reverse-engineer. You may choose to obfuscate your codebase to enhance security. For the native parts of the code in Android, tools like ProGuard can be used for obfuscation.

However, it's important to carefully consider the trade-offs between the level of protection provided and any potential complexity introduced by obfuscation techniques. While obfuscation makes reverse engineering more difficult, it's not an absolute solution, and determined attackers may still be able to decipher the code given enough time and resources.

Remember, the goal is to make the code as challenging as possible to reverse-engineer, even if it can't be completely prevented. By employing minification and obfuscation techniques, you can significantly raise the bar for potential attackers and protect your application's intellectual property and sensitive information.

No sensitive ENV variables

It's crucial to keep in mind that environmental variables in React Native are included in the code bundle that is delivered to the user's device. Therefore, it is imperative never to store sensitive information, such as API keys or server URLs, in environmental variables within your React Native code. Always consider any data included in the code bundle as public and accessible to anyone who obtains the application.

Detect Malicious Environment

If your application handles sensitive data, you may want to consider implementing measures to prevent it from running on devices that are "jailbroken" (iOS) or "rooted" (Android). These devices have been modified to allow users to gain elevated privileges and bypass built-in security features, which can compromise the minimum safe environment required for your application to operate securely.

To detect such malicious environments, there are various solutions available. These detection mechanisms typically involve checking for the presence of certain files, directories, or system modifications that are indicative of a jailbroken or rooted device. By incorporating these checks into your application, you can prevent it from running on compromised devices, reducing the risk of sensitive data being accessed or manipulated by malicious actors.

CI/CD Security

Mobile and React Native applications must be built, signed, and deployed to their respective stores before reaching production. This pipeline should be automated and protected.

While development or testing versions can be built on personal computers and manually distributed to testers, the production pipeline requires additional security measures.

Building, signing, and delivering the application to the store should be performed by secure Continuous Integration/Continuous Delivery (CI/CD) services (such as Expo EAS). Production signing certificates should also be safeguarded, with access restricted to necessary administrators only.

Production-related environment variables should be defined and securely stored within the CI/CD system to prevent unauthorised access or tampering.

Avoid building production bundles on developer laptops, as they may be more susceptible to attacks compared to well-protected servers. By implementing these CI/CD security practices, you can ensure the integrity and confidentiality of your application throughout the deployment process.

Security Processes

Security extends beyond tools and technologies; it also encompasses processes and regular security checks. Depending on the nature of your application and the sensitivity of the data it handles, you may want to consider conducting regular penetration tests or security assessments, potentially involving third-party experts.

Incorporating security considerations into your code review process is crucial. Develop a comprehensive checklist of security checks that reviewers should look for when examining code changes. This list may include items such as input validation, secure data storage, proper authentication and authorisation mechanisms, and protection against common vulnerabilities.

By making security an integral part of your development workflow and regularly assessing your application's security posture, you can proactively identify and address potential weaknesses before they can be exploited by malicious actors.

Remember, security is an ongoing process that requires continuous attention and improvement. Regularly reviewing and updating your security practices ensures that your application remains resilient against evolving threats and maintains the trust of your users.

What is up next?

The security considerations discussed above provide a solid foundation for enhancing the security of your mobile and React Native applications. However, there's always room for further improvement and learning.

To deepen your understanding of mobile application security, you can explore a wealth of resources available online. Some recommended starting points include:

A Final Note

One of the most crucial decisions in application development is determining the appropriate level of security to implement. Security measures can add complexity to the development and management of an application, which translates into increased costs. It's important to recognise that no system is completely foolproof or ad-hoc. Instead, systems can be secured against specific levels of attacks based on the perceived risks and potential consequences.

The decision often revolves around the sensitivity of the data being protected and the types of malicious actors who may attempt to obtain that data. It's essential to strike a balance between the level of security implemented and the practical considerations of cost and usability.

To illustrate this point with an analogy, consider the decision to select a lock to secure a bicycle against theft. Using a lock designed for motorbikes might seem like overkill, but it provides an extra layer of protection. While it's not foolproof, as motorbikes are still stolen, a thief equipped with tools to break a motorbike lock is less likely to target a bicycle secured with such a lock.

Similarly, when determining the appropriate level of security for your application, consider the value and sensitivity of the data you're protecting and the potential threats you're aiming to mitigate. Implement security measures that align with the risks and strike a balance between security, usability, and cost-effectiveness.

Remember, security is an ongoing process that requires continuous evaluation, adaptation, and improvement as threats evolve and new vulnerabilities emerge. By making informed decisions and prioritising security throughout the development lifecycle, you can create applications that provide a strong level of protection while still being practical and usable for your users.

We hope you found the above information useful. Let us know what you think!

Want experts on your side to drive forward your project?
Need a second opinion on something?
We’re here to help!
Find out more
a photo of the Morrow team sat at a dinner table
More insights