Nowadays, we depend on our devices daily for nearly everything we do. Whether it’s for taking photos of our pets, booking holidays, ordering food, shopping online, or banking, a lot of us do this using mobile phones. With the increasing number of mobile applications and services we use, as the data stored in our devices, this, in turn, raises the risk of security exploit or exposure of data to motivated attackers. As developers, we should ask ourselves, “How can we mitigate the security risk and protect the users’ data?“. App security is a really broad topic. However, with the tips below you can be sure, that you are able to develop Flutter apps that can work well with no security vulnerabilities, especially in industries such as Banking, Fintech, HealthTech, or MedTech. Let’s dive into securing a Flutter app, then!
Code obfuscation
What is it?
Code obfuscation is the process of modifying an app’s binary to make it tougher for humans to understand. Obfuscation hides function and class names in your compiled Dart code, making it difficult for an attacker to reverse engineer your proprietary app.
How to do it?
To obfuscate your Flutter app, build a release version using the –obfuscate flag, combined with the –split-debug-info flag. The –split-debug-info flag specifies the directory where Flutter can output debug files. This command generates a symbol map. The apk, appbundle, ipa, ios, and ios-framework targets are currently supported. (macos and aar are supported on the master and dev channels.)
Command to run:
flutter build apk --obfuscate --split-debug-info=/<project-name>/<directory>
Warning!
Flutter’s code obfuscation, when supported, works only on a release build.
Warning vol. 2!
Code that relies on matching specific class, function, or library names will fail. For example, the following call to expect() will not work in an obfuscated binary:
expect(foo.runtimeType.toString(), equals('Foo'));
Prevent jailbroken and rooted devices
What is it?
Jailbreaking refers to the process of removing all restrictions imposed on an iOS device. It allows root access to system files that can be manipulated to enable the installation of apps, themes, and extensions that are not supported by Apple or unavailable for download on the Apple App Store.
Rooting is the process of allowing users of the Android mobile operating system to attain privileged control (known as root access) over various Android subsystems.
How to do it?
To prevent our app from running on jailbreak/rooted devices we can use flutter_jailbreak_detection package. Under the hood, this package uses RootBeer on Android, and DTTJailbreakDetection on iOS. After adding a package to our project, we just need to check:
bool jailbroken = await FlutterJailbreakDetection.jailbroken;
bool developerMode = await FlutterJailbreakDetection.developerMode; // android only.
If you don’t have a rooted device, you can try to run this on an Android emulator – the result should be the same. 😉
Warning!
A user on a rooted/jailbreak device is like God, he can do everything. Even though this method is very accurate, there is no 100% chance of detecting a rooted / jailbreak device.
Secure local storage
What is it?
In almost every app, we need to save something on the user’s device in local storage. The best way to do this is flutter_secure_storage. Under the hood, it uses Keychain for iOS, and it uses AES secret key for Android, which is encrypted with RSA. The RSA key is stored in KeyStore.
How to do it?
We just need to add flutter_secure_storage to our project, create storage and use built-in methods for writing, reading, and deleting data.
//Create storage
final storage = newFlutterSecureStorage();
//Read value
String value = await storage.read(key: key);
// Read all values
Map<String, String> allValues = await storage.readAll();
// Delate value
await storage.delate(key: key);
// Delate all
awit storage.delateAll();
// Write value
await storage.write (key: key, value: value);
In addition to
flutter_secure_storage, we can use local_auth
so users can access data with biometric data.
Screenshot prevent
What is it?
Some apps display sensitive data, so we have to block screenshots or screen recordings. This is typical for banking apps or apps like Netflix.
How to do it?
Blocking screenshots or screen recording is quite easy, but we have to do this natively.
For iOS app:
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.applications(application, didFinishLaunchingWithOptions: launchOptions) }
// <Add>
override func applicationWillResignActive
_ application: UIApplication
) {
self.window.isHidden = true;
}
override func applicationDidBecomeActive(
_ application: UIApplication
) {
self.window.isHidden = false;
}
}
For Android app:
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
window.addFlags(LayoutParams.FLAG_SECURE)
super.configureFlutterEngine(flutterEngine)
}
}
Block app when going background
What is it?
Similar to the previous case, sometimes we don’t want to enter an app without authorization when going from the background. Especially when your app has sensitive data like a credit card number or other financial records.
How to do it?
All we have to do is use WidgetsBindingObserver. Thanks to this class, we can detect when it is in the background and does something when this happens.
class HomePage extends StatefulWidget {
const HomePage();
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.paused) {
//go to initial page
}
}
}
SSL Pinning
What is it?
SSL stands for Secure Socket Layer. SSL certificate creates a trustable connection between the server and the client. This connection ensures that the transmission of the data between the client and the server will be private and secure and minimizes the risk of leaking sensitive data.
How to do it?
First, we need to get .pem file. To do this we need .cer file (you will pin the certificate in every API call) and use this command: openssl x509 -inform der -in certificate.cer -out certificate.pem
Now we need to add our .pem file to our HTTP client.
final dio = Dio();
ByteData bytes = await rootBundle.load('assets/certificate.pem');
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
SecurityContext securityContext = SecurityContext();
securityContext.setTrustedCertificatesBytes(bytes.buffer.asUint8List());
HttpClient httpClient = HttpClient(context: securityContext);
return httpClient;
};
Warning!
If you have to make a browser kind of App, where you have to travel to multiple websites and some unknown websites too, then you can not use SSL Pinning.
Contact
Do you want to find out more about Flutter?
Final thoughts on a Flutter mobile application security
As a general rule, your Flutter team should treat any app as a liability because it operates on the client-side. Once you need to store sensitive information locally, it pays off to comprehend the ins and outs of the framework your teams are incorporating into the project. Flutter is a cross-platform UI framework that runs on a wide variety of devices and operating systems. Yet, each platform still has its own rules of safety and limitations, along with threats. Hence, a Flutter application should always be developed in line with them. Therefore, your team must have sufficient knowledge about each platform for which they are developing an app (iOS, Android, web), and we do at Applover as a Flutter app development company! If you want to learn more about Flutter mobile app development, see our case studies and visit our blog. I hope this article will help you with your further projects!