While @MrUpsidedown offers a few related questions in the comments section, none of them truly answer the part I tried to emphasize here: how to provide API keys securely?
After reading all the answers in the linked questions, I am inclined to write an answer myself.
I am a very fresh Flutter developer who tries to integrate Google maps into a demo application.
To save myself time, I decided to try the most popular Widget library for Google Maps which I was able to find, namely google_maps_flutter
. The library's terse documentation shows Android and iOS usage examples and both illustrate the API Keys are provided inline, as a part of the code base:
import UIKit
import Flutter
import GoogleMaps
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GMSServices.provideAPIKey("YOUR KEY HERE") // <------------------------------------ O--пп
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
<manifest ...
<application ...
<meta-data android:name="com.google.android.geo.API_KEY"
android:value="YOUR KEY HERE"/> <-------------------------------------- O--пп
Most developers know that the API keys must be stored securely. The code from the library documentation does not suite production application. The Google Maps Platform documentation explicitly warns against hard-coding API keys:
Do not embed API keys or signing secrets directly in code.
...
Do not store API keys or signing secrets in files inside your application's source tree.
How do I use this package in a correct, secure way? Is it even possible?
I hear a recurring idea a lot: "The client application can not be ever trusted storing API keys (and similar secrets/credentials) as it can be controlled, manipulated, or reverse engineered by a malicious user." If this is the case, does it mean that I must consider google_maps_flutter
package inherently insecure as long as it requires providing the Google Maps API key explicitly?
I am also aware of the API key restriction. I will definitely use it, but it seems to me that it only reduces the "blast radius" if the API keys are compromised. I don't see, however, how that can prevent the API key leak and misuse by a third party.
Initially, I wanted to pose my question more broadly: Is there a way to securely deliver, store, and consume the secrets on mobile platforms (Android and iOS)?
Below are the conclusions of my research.
The API keys can not be safe on the client device. From the security standpoint, the client device should be treated as compromised.
Therefore, each of the following approaches is UNSECURE:
Storing the API keys directly in the App code. The user can decompile the application, run it under debugger, etc., and read the API keys.
Storing the API keys indirectly in the App. Providing the API keys via environment variables is no different really. The keys get imprinted in the built application's artifacts and are just are readable as in the previous approach.
Delivering the API keys to the App from a controlled service. The user can mess with the app the way tampering results in intercepting key on reception from the remote server. In other words, the classic man-in-the-middle attack.
Yes, you heard it right. Your API keys should live in the service only you can access. That service should work as a proxy for all the upstream API calls your app is making. You don't expose the keys to the client device, thus they can not be stolen from the device's environment.
However, a new question arises immediately: how do you limit the access to your API endpoint? Well, this is an open question, and is out of scope of this answer. I want to note that if you add an own API key for your service, it is in a way leading you to the chicken or the egg problem. Still, own API key is a step forward because you could throttle client requests coming from specific clients. In other words, you ensure that you're in control of things and you don't exceed the quota for the upstream service such as Google Maps API.
Yet, I don't know how you can reliably identify that the caller is your app (if you ever need that). If I understand correctly, the app id is not reliable due to the previous section — client device can not be trusted.
google_maps_flutter
The google_maps_flutter
library only demonstrates in its documents provisioning of the API Keys by means of hard-coding them in the App (see appendix 1). That is insecure.
As somebody noticed in the flutter's GitHub issues section, nowhere the google_maps_flutter
recommends hard-coding the API keys in the app for production environments. However, it fails to illustrate or suggest where else can the API keys be provided from.
More importantly, it does not show how to feed the response/data from your proxy server into the widget itself and this is a huge problem.
I was recommended to restrict the API keys with Google Maps API service itself. Here is what these restrictions look for Android and iOS:
Android apps
Add your package name and SHA-1 signing-certificate fingerprint to restrict usage to your Android app.
iOS apps
Accept requests from the iOS app with the bundle identifier that you supply.
I fail to see how it can possibly work as a reliable instrument. Can't the SHA-1 keys and the bundle identifier be tampered on client's phone?! (I believe, they can.)
google_maps_flutter
in a secure way, that will avoid the API key compromise.Android
Specify your API key in the application manifest android/app/src/main/AndroidManifest.xml:
<manifest ... <application ... <meta-data android:name="com.google.android.geo.API_KEY" android:value="YOUR KEY HERE"/>
iOS
Specify your API key in the application delegate ios/Runner/AppDelegate.m:
#include "AppDelegate.h" #include "GeneratedPluginRegistrant.h" #import "GoogleMaps/GoogleMaps.h" @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [GMSServices provideAPIKey:@"YOUR KEY HERE"]; [GeneratedPluginRegistrant registerWithRegistry:self]; return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With