How Do I Handle the Sensitive Key in Flutter?

Sailesh Shakya
codingmountain
Published in
3 min readSep 29, 2023
Photo by Kelly Sikkema on Unsplash

Hello guys, I want to share my experience with handling the secret key in Flutter. In Flutter, as in any other development environment, secret API keys are used to access external services or resources securely. These keys are typically used to authenticate your application with third-party APIs or services. Keeping these keys secret is crucial to prevent unauthorized access or misuse.

Let me explain how I handle secret Sensitive keys in Flutter.

  • Using .gitignore file
  • Using env file
  • Using — dart-define

Using .gitignore file

The first thing I always do in my project is to create the .gitignore file and include all the files that will store the sensitive data. By this, my sensitive file will not push to git servers. If you forget in the initial push then It will be there forever even if you include the file in .gitignore in the second commit. It will be there in the commit history of git. This is a thing to remember for us

Using .env file

  • .env is a popular file format that was introduced to give developers a single secure place to store sensitive application secrets such as API keys

To use this with Flutter, we can add a .env file at the root of the project:

# example .env file API_KEY=a1b2c33d4e5f6g7h8i9jakblc

And since this file contains our API key, we should add it to .gitignore:

# exclude all .env files from source control
*.env

then add the package that handles the .env file as Dart doesn't handle it. I use the ENVied package for this.

here we make an env.dart file that looks like this

import 'package:envied/envied.dart';
part 'env.g.dart';@Envied(path: '.env')
abstract class Env {
@EnviedField(varName: 'API_KEy')
static const ApiKey = _Env.tmdbApiKey;
}

Then, we can run this command:

flutter pub run build_runner build — delete-conflicting-outputs

then a new file will be generated that is env.g.dart

I use this option as this package has an obfuscate option where I can do

import 'package:envied/envied.dart';
part 'env.g.dart';@Envied(path: '.env')
abstract class Env {
@EnviedField(varName: 'API_KEy', obfuscate: true)
static final ApiKey = _Env.tmdbApiKey;
}

Using — dart-define

Sometimes we have to add spiky direct to androidmanifest or Info.plist then .env variable can’t be used in those parts. For those parts, I use — the dart-define method. For example, for Google Map API integration with Flutter, we need a key that we put on the Android manifest

flutter run \ — dart-define GOOGLE_KEY=a1b2c33d4e5f6g7h8i9jakblc

then we can access both the dart and the native side. In Dart, we can access it like this on the Dart side

const tmdbApiKey = String.fromEnvironment('GOOGLE_KEY');
if (tmdbApiKey.isEmpty) {
throw AssertionError('KEY is not set');
}

For the Android native side:

Go to android/app/build.gradle and use the following code to parse every possible variable from — dart-define and make it available as a key: You can use the value map in a variety of ways.

def dartEnvironmentVariables = []
if (project.hasProperty('dart-defines')) {
dartEnvironmentVariables = project.property('dart-defines')
.split(',')
.collectEntries { entry ->
def pair = new String(entry.decodeBase64(), 'UTF-8').split('=')
[(pair.first()): pair.last()]
}
}

then in the Android manifest

<meta-data
android:name="com.very.secret.api"
android:value="${GOOGLE_KEY}" />

And on the Ios side:

First, add the following to your ios/Runner/Info.plist file:

<key>DART_DEFINES</key>
<string>$(DART_DEFINES)</string>

This allows us to read the contents of the raw — dart-define final value from within our Swift code. The other option, using xconfig, makes the values available exclusively within Xcode. Then include the following in your didFinishLaunchingWithOptions override:

let dartDefinesString = Bundle.main.infoDictionary!["DART_DEFINES"] as! String
var dartDefinesDictionary = [String:String]()
for definedValue in dartDefinesString.components(separatedBy: ",") {
let decoded = String(data: Data(base64Encoded: definedValue)!, encoding: .utf8)!
let values = decoded.components(separatedBy: "=")
dartDefinesDictionary[values[0]] = values[1]
}

It’s equivalent to what we added in build.gradle above, it takes the raw values and converts them from base64 into a key: value dictionary that we can use to set any SDKs as follows:

VerySecretApi.start(withApiKey: dartDefinesDictionary["GOOGLE_KEY"]!, in:application, withLaunchOptions:launchOptions)

Hopefully, this article will be useful to you all. Thank you for reading :)

--

--