Most of us depend on our devices daily for almost everything we do. Whether it's for taking photos of our WFH setup, ordering food, shopping, and even online banking, a lot of us, if not all, do this using our mobile phones.

With the increasing number of digital products and services we use, so as the data stored in our devices, this, in turn, increases the risk of security exploit or exposure of data to motivated attackers.

As developers, we should ask ourselves, "How can we mitigate the risk of a security exploit, protect the data of our users and build trust for the apps that we are building?".

I am not a security expert but I do have experience building and shipping apps to millions of users with little to no security breaches at all. Also, I currently work as a developer for a FinTech company, building banking apps using Flutter.

10 Tips to Secure Your Flutter Apps for Mobile

Disclaimer: This article does not make your apps 100% secure, but I hope it will reduce the risk for any security exploit on your apps and increase the protection of your users' data.

1. Stay up-to-date

Getting your Flutter SDK, plugins, and packages up-to-date is the easiest and one the best ways to keep the apps you are building secure. Google Flutter Team releases security fixes and patches for vulnerabilities found on the Flutter framework.

If you think you found an issue that is unresolved or hasn't been filed yet on the Flutter repo, report it to the Google Flutter Team.

2. Obfuscate code

The compiled binaries and code of your apps can be reversed engineered. Some of the things that can be exposed include the strings, method and class names, and API keys. These data are either in their original form or in are in plain text.

Obfuscation is the practice of making something difficult to understand.

Here are some good examples from this for code obfuscation:

Rename obfuscation
String encryption

Dart

Dart comes with code obfuscation argument --obfuscate in addition to the build command:

Example 1. Android app bundle without splitting:

flutter build appbundle --obfuscate --split-debug-info=/<directory>

Example 2: Β Android app bundle with splitting:

flutter build appbundle --target-platform android-arm,android-arm64,android-x64 --obfuscate --split-debug-info=/<directory>

NOTE: This is only for Dart code, you have to handle platform-specific obfuscation for the native code, eg. using ProGuard for Android.

Android

In your /android/app/build.gradle file, add the following:

android {
	...
	buildTypes {
		release {
			signingConfig signingConfigs.release
			minifyEnabled true
			useProguard true
			proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
		}
	}
}

Create a ProGuard configuration file in /android/app/proguard-rules.pro:

# Flutter
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.**  { *; }
-keep class io.flutter.util.**  { *; }
-keep class io.flutter.view.**  { *; }
-keep class io.flutter.**  { *; }
-keep class io.flutter.plugins.**  { *; }

With ProGuard, it does not only obfuscate your code but also helps you shrink the size of your Android apps.

iOS

If you are using Objective-C or Swift and building for iOS, the compiler strips the symbols and applies optimizations to your code, making it already harder for the motivated attacker to read the compiled output of your code.

There are paid tools that I found helpful if you want to obfuscate your code: iXGuard and Vermatrix.

Although obfuscation is regarded as one of the best ways to prevent reverse-engineering on your apps, some de-obfuscation techniques are out there. The question to have in mind, "What can the motivated attacker see on my code and use against me or my users?".

3. Secure API keys

API keys come in multiple formats but often for mobile, you have it in a form of a String object. If not encrypted or if your code is not obfuscated, it will a lot be easier for any motivated attacker to use your API keys and abuse them.

Server-side

You can restrict API access to an API key.

For example, if you are using Google APIs or Firebase, you may want to restrict the access for your apps or be used specific services only.

Go to your console, select the API & Services, then restrict any services that you have depending on how you deemed it necessary.

Restricting Google API access for Android with the given package name and SHA-1 fingerprint

For services that don't have server-side restrictions, you can move the integrations to the backend and expose the methods via RESTful APIs.

But if this is not possible, another way you can do is to encrypt/decrypt API keys on runtime, eg. when the user is authenticated. It doesn't guarantee that your keys will be 100% secured, but it adds an extra layer of protection and make it harder for a motivated attacker to read it.

Development

Do not track API keys on your repository. Especially for open-source projects. You should enforce the use of environment configuration file/s.

The files I'm talking about can be a .env file that contains all the values for the API keys or URLs that your apps connect to. For example, using flutter_dotenv.

flutter_dotenv | Flutter Package
flutter_dotenv - Easily configure any flutter application with global variables using a `.env` file.

.env

SOME_API_KEY=SOME_VALUE
SOME_HTTP_URL=SOME_VALUE

lib/main.dart

import 'package:flutter_dotenv/flutter_dotenv.dart';

final baseUrl = env['SOME_HTTP_URL'];
NOTE: In the compiled output of your code, these values can still be exposed, although the risk is reduced by not tracking them on version control like Git.

You can issue the environment configuration file/s to your team using LastPass or a similar password manager.

Do not share API keys, tokens, or any sensitive data in plain-text on your communication tools like Slack or Discord.

Avoid using Firebase Remote Config

Firebase Remote Config is a service that allows you to make changes on-the-fly without publishing a new build, eg. using bool values as toggles and for A/B testing.

Although it's possible, you should not use this service for storing sensitive data, eg. with the hope of changing API keys. It is strictly not recommended for that use case.

4. Restrict network traffic

Most apps, if not all, are connected to the internet, whether to a third-party service provider or to their own servers. Typically, the exchange of information happens on a Transport Secure Layer, a.k.a. TLS to provide a secure connection between your mobile apps and your servers.

Trusted network

One the easiest way to restrict network traffic is through whitelisting domains that your apps are allowed to connect.

For Android:

res/xml/network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config>
        <domain includeSubdomains="true">example.com</domain>
        <trust-anchors>
            <certificates src="@raw/my_ca"/>
        </trust-anchors>
    </domain-config>
</network-security-config>
https://developer.android.com/training/articles/security-config

For iOS:

ios/Info.plist

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <false/>
  <key>NSExceptionDomains</key>
  <dict>
    <key>cocoacasts.com</key>
    <dict>
      <key>NSIncludesSubdomains</key>
      <true/>
      <key>NSExceptionAllowsInsecureHTTPLoads</key>
      <true/>
    </dict>
  </dict>
</dict>
https://cocoacasts.com/how-to-add-app-transport-security-exception-domains

Certificate Pinning

In addition to the trusted domains that your apps use, you can also implement certificate pinning for your apps to restrict the secure connection to particular certificates. This ensures that the connection between your apps and your servers is trusted and authentic.

Photo credits: TLS Certificate Pinning 101 - Nettitude Labs

Without certificate pinning, often the motivate attacker can eavesdrop or tamper data when on transit using hacked or self-signed certificates.

Here are some in-depth guide on how you can implement certificate pinning for iOS and Android.

5. Limit permissions

Permissions is way for your apps to access hardware or native APIs of your users' devices. In choosing a plugin, you should always check whether the plugin has dubious permission request.

For example, if it's a audio-only package, wouldn't it be weird if the package requests for a camera or network permission?

6. Secure user data

Personally identifiable information, a.k.a. PII, is the most critical data that you don't want to store on your apps, because when expose, you're company is in big trouble.

But there are instances where PII is needed, eg. for offline-first apps. When needed, you can use flutter_secure_storage for storing PII or other sensitive data such as auth token.

flutter_secure_storage | Flutter Package
flutter_secure_storage - Flutter Secure Storage provides API to store data in secure storage. Keychain is used in iOS, KeyStore based solution is used in Android.

In a nuthsell, Flutter Secure Storage is a package that uses Keystore for Android and Keychains for iOS, both of which are considered a standard in terms of securing sensitive data to your users' mobile devices.

Android Keystore Architecture

Caching

For storing other sensitive data, other than PII, I would recommend the use of Hive for performance gain, although requires more setup. It uses AES-256 encryption which helps you secure the data of your users from an unwanted exploit or tampering.

hive | Dart Package
hive - Lightweight and blazing fast key-value database written in pure Dart. Strongly encrypted using AES-256.
To secure the data of your users, do not store it in plain text.

7. Integrate local authentication

local_auth | Flutter Package
local_auth - Flutter plugin for Android and iOS devices to allow local authentication via fingerprint, touch ID, face ID, passcode, pin, or pattern.

8. Secure your developer identity

TODO

9. Secure your CI infrastructure

Depending on whether your CI infrastructure is self-hosted or using services like Github Actions, you should be aware of what's going on in your workflows.

Latest updates

To ensure that your apps are running in a secured environment, you should always keep your VMs up-to-date or be on a lookout for any security vulnerabilities (eg. OS updates for Linux for Android or macOS VMs for iOS)

Secrets

I talked about this earlier, you should not commit API keys or related sensitive data (or secrets) in your code, instead (for CI) you add them on the secrets settings of your project. Other services like Bitrise provide the same option for storing secrets.

10. Enforce strict access control

This is not Flutter related, but it's good to have for your team.

Access Board

If you don't have it yet, create an Access board for your team and that contains the Β tickets created for each request made to access service/s. This allows your team to have transparency and record on who's requesting for access, when, who's provisioning it and why, etc.

Sample access board on Github

Password Manager

Use password manager like Lastpass to secure the way you share or provision credentials. You don't want to see a plain text API key on the communication tools that you are using.

Others

Secure payments

Developer machine

Jailbroken and rooted devices

Summary

I do hope the tips that I've shared here helped you understand different areas where you can improve in terms of security and data privacy.

The takeaway for this article is to not just the application of these tips itself, but the mindset that it creates.

Now ask yourself, "How can I mitigate the risk of security exploit and protect the data and build trust with my users"?

If you liked this article or have any questions, feel free to reach out on Twitter, @joshuamdeguzman ✌

Resources