After working with React Native for several years, I've put together key security practices you can use to evaluate your project. Apps can't be 100% secure, but you can make hacking difficult and expensive. Here's a checklist that covers the main attack surfaces and what to do about them.
1. SSL Pinning
Problem: API calls can be intercepted with tools like Burp Suite or Charles Proxy, exposing request payloads and responses—including tokens and sensitive data.
Solution: Implement certificate pinning so the app only trusts your server's certificate (or public key). That way, even if someone installs a custom CA, man-in-the-middle traffic won't be accepted.
Use react-native-ssl-pinning (or a similar library) to pin your API domain. Pin the certificate or public key hashes and fail closed if they don't match. Remember to update pins before cert rotation so the app doesn't break.
2. Reverse Engineering
Problem: APK (Android) and IPA (iOS) can be decompiled using tools like APKTool, jadx, or Hopper, revealing business logic, API shapes, and sometimes secrets.
Solution:
- Android: Enable ProGuard or R8 in release builds to obfuscate and shrink code. Remove debug symbols and strip unnecessary metadata.
- iOS: Strip symbols in release builds (Xcode does this by default when you build for release). Avoid storing secrets or critical logic in the client when possible.
- General: Move sensitive logic and decisions to the backend. Treat the client as untrusted; validate and authorize on the server.
3. Secure Local Storage
Problem: AsyncStorage (and similar unencrypted storage) can be read on rooted (Android) or jailbroken (iOS) devices, leaking tokens and other sensitive data.
Solution: Use platform-backed secure storage:
- iOS: Keychain (encrypted, tied to the device and optionally to biometrics).
- Android: Keystore (hardware-backed when available).
Libraries like react-native-keychain or react-native-encrypted-storage expose a single API for both platforms. Store tokens, refresh tokens, and other secrets there—not in AsyncStorage or plain files.
4. Screenshot / Screen Recording
Problem: Sensitive screens (OTP, payment forms, private content) can be captured via screenshots or screen recording and leak outside the app.
Solution:
-
Android: Prevent screenshots on sensitive screens using
FLAG_SECURE(or packages like react-native-screenshot-prevent) so the system doesn't allow screenshots or recording of that window. -
iOS: Detect screen recording using
UIScreen.isCapturedand hide or blur sensitive views (e.g. OTP field, card number) while recording is active.
Tip: react-native-screen-capture-secure can help detect and block screen capture on both platforms. Use it on screens that show PII, payment details, or one-time codes.
5. Root / Jailbreak Detection
Problem: Rooted (Android) or jailbroken (iOS) devices can bypass app protections, read memory or storage, and tamper with the runtime.
Solution: Detect compromised devices using a library like react-native-jail-monkey (or similar). Use the result to:
- Block access entirely for high-risk apps (e.g. banking), or
- Limit features / show a warning and log the event for analytics.
Balance security with UX: some users root for legitimate reasons, so define a clear policy (block vs. warn vs. allow with reduced functionality).
6. Code Obfuscation
Problem: The JS bundle and native code can be inspected to extract API keys, endpoints, or business logic.
Solution:
- JavaScript: Use javascript-obfuscator (or a Metro/bundler plugin that integrates it) to obfuscate the release bundle. Don't rely on obfuscation to protect real secrets—move those to the backend or use short-lived tokens.
- Native: On Android, ProGuard/R8 obfuscate Java/Kotlin. On iOS, strip symbols and avoid embedding secrets in the binary. Obfuscation raises the bar; it doesn't make reverse engineering impossible.
7. API Security
Problem: Exposed API keys, long-lived tokens, or unvalidated requests can be abused by attackers who extract them from the app or intercept traffic.
Solution:
- Use short-lived tokens (e.g. JWT with short expiry) and refresh them via a secure, authenticated flow. Don't put long-lived API keys in the client if they can be moved to the backend.
- Validate every request on the backend—auth, authorization, and input validation. Never trust the client.
- Optionally sign payloads (e.g. HMAC) for critical operations so the server can detect tampering.
8. Runtime Integrity / Tamper Detection
Problem: The APK or IPA can be modified (re-signed, patched) to bypass checks, remove root detection, or inject code.
Solution: Verify app signature (and optionally integrity) at runtime:
- Android: Check that the app is signed with your release keystore (e.g. compare signature to a known value).
- iOS: The system enforces code signing; you can add checks for jailbreak and for tampering with key resources.
If the app appears modified or running in an unexpected environment, block or limit functionality and report. This makes casual tampering harder; determined attackers may still find ways around it.
9. Secure Deep Links
Problem: Malicious apps or links can trigger your deep links with crafted parameters and try to open sensitive screens (e.g. password reset, payment) without proper auth.
Solution:
- Validate all parameters—type, format, and allowed values. Reject or sanitize anything suspicious.
- Authenticate the user before opening sensitive screens. Don't rely on the link alone to grant access; verify session or token on the server if needed.
- Use verified app links (Android App Links / iOS Universal Links) so only your domain can open your app, reducing phishing via custom URL schemes.
10. CodePush / OTA Updates
Problem: Over-the-air updates (e.g. CodePush, EAS Update) can deliver new JS (and sometimes assets). If the update channel or signing isn't enforced, an attacker could push malicious code to users.
Solution:
- Only allow signed updates from your own backend or the official CodePush/EAS endpoint. Verify the signature or token before applying an update.
- Verify the source of the update (e.g. same app ID, same deployment key) and use HTTPS and certificate pinning for the update endpoint.
- Restrict who can publish updates and audit release history. Treat OTA as a privileged pipeline.
Summary
| # | Area | Main risk | Key mitigation |
|---|---|---|---|
| 1 | SSL | MITM, traffic interception | Certificate/public key pinning |
| 2 | Reverse engineering | Logic and secrets exposed | ProGuard/R8, strip symbols, move logic to backend |
| 3 | Local storage | Token/data leak on compromised devices | Keychain / Keystore (react-native-keychain, etc.) |
| 4 | Screenshot/recording | Sensitive UI captured | FLAG_SECURE, UIScreen.isCaptured, blur/hide |
| 5 | Root/jailbreak | Bypass of app protections | Detect and block or limit (e.g. react-native-jail-monkey) |
| 6 | Code obfuscation | Keys and logic extracted | JS obfuscator, ProGuard/R8; no secrets in client |
| 7 | API | Abuse of keys and endpoints | Short-lived tokens, server-side validation, signing |
| 8 | Runtime integrity | Tampered app bypasses checks | Signature verification, tamper detection |
| 9 | Deep links | Unauthorized access to screens | Validate params, authenticate user |
| 10 | OTA updates | Malicious update delivery | Signed updates only, verify source |
Use this list to review your React Native app and prioritize the items that matter most for your data and threat model. If you've got practices that have worked well for you, share them in the comments.
Top comments (0)