The cross-platform trade-off
About 70% of the Nigerian fintech apps we assess at Simpa Labs are built on either Flutter or React Native. The appeal is obvious—a single codebase, faster iteration, lower cost. But these frameworks create an abstraction layer between your code and the operating system, and that layer has its own attack surface.
Native Android and iOS apps interact directly with platform security primitives. Cross-platform apps interact through bridges—Flutter's platform channels and React Native's JavaScript bridge. Every message that crosses that bridge is a potential interception point on a compromised device.
React Native: the JavaScript bundle problem
React Native apps ship a JavaScript bundle (often named index.android.bundle) inside the APK. This bundle contains your entire application logic in readable JavaScript. An attacker who extracts the APK with apktool can open that file in any text editor and read your business logic, API endpoints, validation rules, and—far too often—hardcoded secrets.
We have seen Nigerian fintech apps where the entire transaction signing logic was visible in the JS bundle. The attacker didn't need to reverse-engineer compiled code. They just read it.
Hardening React Native
Use Hermes as the JavaScript engine. Hermes compiles JS to bytecode at build time, which is significantly harder to read than raw JavaScript. Combine this with react-native-obfuscating-transformer or metro-minify-terser for additional obfuscation. Neither is foolproof, but together they raise the cost of reverse engineering substantially.
For secrets, never bundle them in the JS layer. Use react-native-keychain for credential storage (backed by Android Keystore and iOS Keychain) and react-native-config for build-time environment injection. The secret should never exist in the JavaScript runtime.
Flutter: Dart snapshots and platform channels
Flutter compiles Dart to native ARM code via AOT (Ahead-of-Time) compilation, which is inherently more resistant to casual reverse engineering than React Native's JS bundles. But "more resistant" is not "immune."
Tools like Doldrums and reFlutter can parse Flutter's snapshot format, extract class names, method signatures, and string constants from release builds. In a recent assessment, we recovered full API endpoint paths, internal error messages, and configuration values from a Flutter fintech APK using these tools alone.
Platform channel security
Flutter communicates with native Android/iOS code through MethodChannel, EventChannel, and BasicMessageChannel. These channels are string-identified and unencrypted. On a rooted device, an attacker using Frida can hook into the channel handler, intercept every message, modify parameters, and replay calls.
Unvalidated platform channel inputs
In multiple Nigerian fintech pentests, we found that native-side handlers trusted all data received from the Dart side without validation. An attacker who hooks the MethodChannel can send arbitrary parameters—changing transaction amounts, bypassing biometric checks, or escalating privileges—because the native code assumes the Dart layer already validated the input.
The fix: treat every platform channel message like an untrusted API request. Validate types, ranges, and business rules on the native side. Never pass raw secrets (keys, PINs, tokens) through platform channels—perform cryptographic operations entirely within native modules where possible.
Secure storage: the right way
Both frameworks offer libraries that wrap platform-native secure storage, but teams frequently use the wrong one.
Flutter
Use flutter_secure_storage, which delegates to Android Keystore (AES-CBC with PKCS7) and iOS Keychain. Avoid shared_preferences for anything sensitive—it writes plain XML to disk on Android.
React Native
Use react-native-keychain or expo-secure-store. Avoid AsyncStorage entirely for tokens, PINs, or session data—it stores unencrypted JSON in SQLite on Android.
Both
Set biometric access controls on stored credentials. On Android, use setUserAuthenticationRequired(true). On iOS, use kSecAccessControlBiometryCurrentSet. This ties decryption to active biometric verification.
Code obfuscation is not optional
Flutter supports Dart obfuscation with --obfuscate --split-debug-info flags. This randomises class and method names in release builds. React Native requires third-party tools like javascript-obfuscator or the Hermes bytecode compiler.
Obfuscation alone does not prevent reverse engineering—but it transforms a 30-minute analysis into a multi-day effort. For fintech apps handling real money, that cost increase matters. Combine obfuscation with anti-tampering and root/jailbreak detection for layered protection.
Is your Flutter or React Native app leaking secrets in the build artifact? Our mobile pentest will tell you exactly what an attacker can see.
Book a Mobile App PentestNative module risks
Both frameworks allow calling native code for performance-critical or platform-specific operations. In fintech, this typically means biometric authentication, cryptographic signing, or hardware-backed key generation. These native modules are powerful but introduce risk if poorly implemented.
Common mistakes we find: native modules that log sensitive data to Logcat/Console, modules that don't clear sensitive byte arrays from memory after use, and modules that expose internal methods to the JavaScript/Dart layer without access controls. Each of these is exploitable on a rooted device.
Network security configuration
Both frameworks rely on the platform's network stack for TLS, but the default configurations are not fintech-grade. React Native on Android allows cleartext traffic by default in debug builds, and that configuration sometimes ships to production. Flutter respects the Android network_security_config.xml, but many teams never create one.
For any fintech app, implement certificate pinning, disable cleartext traffic explicitly, and restrict trusted CAs to only those used by your backend. This prevents MitM attacks even on compromised networks—a real concern in Nigeria where public Wi-Fi networks are frequently hostile.
What to do before your next release
Run through the mobile app pentest checklist we have published, with specific attention to cross-platform concerns. At minimum: enable code obfuscation, migrate secrets out of the JS/Dart layer, validate platform channel inputs on the native side, implement certificate pinning, and use hardware-backed secure storage for all tokens and credentials.
If your team built the app but has never had it independently tested, you are guessing at your security posture. Cross-platform frameworks make shipping easy. They also make shipping vulnerabilities easy.
Related reading
Blog: Hardcoded API Keys in Mobile Apps · Reverse Engineering Android Fintech Apps · Certificate Pinning in Mobile Banking
Guides: Mobile App Pentest Nigeria · OWASP for Fintech · Fintech Security Checklist
Services: Penetration Testing · Authentication Security
Frequently asked questions
{faq.q}
{faq.a}