Storing your secret keys in Flutter

“Numbers on metal deposit boxes in a bank” by Tim Evans on Unsplash

EDIT (12/11/2018): This post is meant to keep your secret keys out of version control, not to secure them entirely. Even in native development (at least in Android) you have to take extra steps just to make them harder to get, because someone experienced can decompile your app and retrieve the keys as shown in this post. The most secure way to keep your keys as secret, is to NEVER put them in your app.

I made this post because I (as some other people) had this same problem in Flutter. You want to store your API keys in your project, but don’t know where as Flutter lacks something like local.properties in Android.

The most recommended approach I have found is using text assets. In Flutter you just need to load your file containing your secret keys as if your were loading any other asset.

For example,

import 'dart:async' show Future;
import 'package:flutter/services.dart' show rootBundle;

Future<String> loadAsset() async {
return await rootBundle.loadString('assets/config.json');
}

As is show here in Flutter docs: https://flutter.io/assets-and-images/#loading-text-assets

That is the shortest way. You can load a json, then parse it with dart:convert, and have your keys. But for this example, let’s make it a little more elaborated.

First, let’s create a file called secrets.json that will keep our secret API keys. And store it in the root directory of our project. Remember not to commit your secrets.json file to version control.

{
"api_key": "random_api_key"
}

Next, we need to write an entry in pubspec.yaml pointing to our secret file.

assets:
- secrets.json

Now, let’s define the class that will keep our keys, let’s say it’s called Secret.

class Secret {
final String apiKey;
  Secret({this.apiKey = ""});
  factory Secret.fromJson(Map<String, dynamic> jsonMap) {
return new Secret(apiKey: jsonMap["api_key"]);
}
}

Then a SecretLoader.

import 'dart:async' show Future;
import 'dart:convert' show json;
import 'package:flutter/services.dart' show rootBundle;
class SecretLoader {
final String secretPath;

SecretLoader({this.secretPath});
  Future<Secret> load() {
return rootBundle.loadStructuredData<Secret>(this.secretPath,
(jsonStr) async {
final secret = Secret.fromJson(json.decode(jsonStr));
return secret;
});
}
}

I used loadStructuredData because this function already were designed for that use case, you just need to send a parser.

After that, you can just use your SecretLoader like this:

Future<Secret> secret = SecretLoader(secretPath: "secrets.json").load();

From then on, it’s up to you how to use this knowledge. If you’re using streams, you can convert it to stream with .toStream(). Or you can use it in your root widget tree with FutureBuilder.


I hope this brief post helps you with your projects.