When you’re building an Android, iOS, or Flutter Application, it’s important to keep your API keys secure. This means that API keys should not be checked into your version control tool. But still, you need the ability to reference them in your local environment as well as on CI.
In this article, I’m assuming that on CI, keys are stored as environment variables on the machine you’re building your project. I’m skipping this part because there are plenty of ways to set environment variables. I want to focus on how to make a reference in the code to the keys on both: local machine and CI.
Android
According to Google Documentation to store API keys securely it is recommended to use local properties. If you don’t have the file local.properties
in your project, it should be located in the android project root directory. Make sure to add it to intentionally untracked files in your version control tool. Now you can add here a new variable. Note that the value of the key should NOT be wrapped by either single or double quote characters.
googleMapsApiKey=YOUR_GOOGLE_MAPS_API_KEY_STRING_VALUE
The next step is to define the variable in the Gradle file build.gradle
located in the \app
directory to make it work on local and CI machines:
def localProperties = new Properties()
localProperties.load(new FileInputStream(rootProject.file("local.properties")))
def googleMapsApiKey = localProperties.getProperty('googleMapsApiKey')
if (googleMapsApiKey == null) {
googleMapsApiKey = System.getenv("GOOGLE_MAPS_API_KEY_ENVIRONMENT_VARIABLE")
}
And add it to manifestPlaceholders
in the defaultConfig
:
manifestPlaceholders = [
googleMapsApiKey : googleMapsApiKey
]
And that’s it. Now you can refer to the key in the AndroidManifest.xml
.
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="${googleMapsApiKey}" />
iOS
For iOS, I will use Xcode build configuration files. This approach requires two config files Release.xcconfig
and Debug.xcconfig
in the project directory.
For the iOS project, you can use Xcode to create the configuration file by “File > New > File…” and finding in section “other” Configuration Setting File.
For the Flutter project, you can simply add these files in the ios\Flutter
directory.
Make sure to add the Debug.xcconfig
file to intentionally untracked files in your version control tool.
For Release.xcconfig
it needs to refer to the environment variable:
GOOGLE_MAPS_API_KEY=$(GOOGLE_MAPS_API_KEY_ENVIRONMENT_VARIABLE)
And for Debug.xcconfig
set the actual key. Note that the value declared key should NOT be wrapped by either single or double quote characters.
GOOGLE_MAPS_API_KEY=YOUR_GOOGLE_MAPS_API_KEY_STRING_VALUE
Next, in the Info.plist
refer to the variable in the configuration file:
<key>googleMapsApiKey</key>
<string>$(GOOGLE_MAPS_API_KEY)</string>
and finally, in the AppDelegate.swift
it’s possible to refer to the variable:
GMSServices.provideAPIKey(Bundle.main.object(forInfoDictionaryKey:"googleMapsApiKey") as? String ?? "");
Summary
Of course, everyone should remember that at end of the day API Keys will be somewhere in the binary code of our application. Defending ourselves from people who know what they are doing is impossible but at least it’s possible to make their lives a little bit harder. AndroidManifest.xml
and Info.plist
files are relatively easy to convert from binary to text. There are alternatives to the proposed solutions like Cocoapods-Keys or worst case scenario keeping keys in source code :)
It is worth reminding the fact that usually the same key is used for both Android and iOS Applications. It’s important to keep at least a minimum amount of security on both sides because it doesn’t matter which one will leak a key.
Additional knowledge base
For more information, you can check out the documentation for adding keys to the Android project for setting up project Android project and for setting up an iOS project. I also recommend reading about secret management on iOS. If you are interested in understanding Apple binaries, take a look at this article on Medium, which casts a little bit more light on the subject.
Update: In Flutter 2.5.3 there is a new feature added called --dart-define which allows to setup keys and secrets for Android and iOS directly while building a project. More about it you can read in this article.