92

Which is the correct way(best practice) of adding secret API keys in flutter in case I want to push the code on github. I've made a simple app that consumes an API but I used the key in a crud way just to test whether the app is working. Usually from my experience developing applications in the back-end, Keys are stored somewhere and in different file then one would simply import it to the required file that needs the API_KEY and exclude the file in .gitignore file.

So far I have also implemented this approach:

Folder tree

-lib
  -auth
    -keys.dart
    -secrets.json 

secrets.json

This is where I will add the KEY and specify this file in .gitignore to be excluded from being added in github when I push my code.

//Add API KEY HERE
{
  "api_key": "ee4444444a095fc613c5189b2"
}

keys.dart

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


class Secret {
  final String apikey;

  Secret({this.apikey=""});

  factory Secret.fromJson(Map<String, dynamic>jsonMap){
    return new Secret(apikey:jsonMap["api_key"]);
  }
}


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 feel like this approach is too much. I would like to get suggestions of a better approach.

7
  • 9
    If this shouldn't be shared; ideally it shouldn't be on the client. Ultimately once you ship your app, peoples can just decompile it to get everything within. Commented Aug 17, 2018 at 12:21
  • 1
    I'm using an API that requires me to add the key so that I get the payload I need. The app is consuming the API and add a key is required to access the payload. Where can I store the keys so that I access them when needed? Commented Aug 17, 2018 at 12:32
  • I like this method of doing things. I've done very similar things in my apps. I will also make a main_dev.dart and main_prod.dart -- each file will be targeted for running that version of the app. In each file, i will import the corresponding secrets.json and use it throughout the app. Commented Jul 21, 2019 at 4:20
  • 6
    if you rely on gitignored file with secrets, just make a dart file with static const keys. Much easier to use Commented Jun 22, 2020 at 6:07
  • 1
    but isn't this negligent for production apps to do? In other words if I want to provide a secure app to my user's I always need to build my own backend, thus making all flutter setups provided by google obscure? Commented Oct 20, 2023 at 12:42

10 Answers 10

31

API Keys must be hard coded.

Why?

Because it's impossible to retrieve the key from an external source without having a key to that source, and you can't store it securely at the OS level without having the key within the app.

Example

When you connect your app to a Firebase project or Google Cloud servers, you basically authenticate using a hard-coded API key, that you have downloaded into your app when initiating your cloud project (read more).

Security

There are two essential steps to secure your critical assets:

  1. Keep your secret keys out of version control.
  2. Use obfuscation to make it difficult for attackers to reverse engineer your application, and reveal your API Key.

IMO, those are the only steps you can take to secure your app API Key.

Sign up to request clarification or add additional context in comments.

2 Comments

Agreed, best answer. Though, I might add
For anyone seeing this now, check @RobbB's answer and my clarification before deciding if this is good enough.
8

For secure storage you have to rely on the corresponding native platforms, both iOs and Android provide a mechanism to securely store keys. You can implement it by yourself and use the flutter channels to obtain and store the keys. Information about this mechanism can be read here:

Android Keystore

iOs KeyChain

Also, you can use this flutter plugin, which uses the services mentioned above and provides a dart object to access the secure storage.

2 Comments

This does not help since you'd put the API Secret in the code at some point and decompiling the APK would still reveal the Secret!!!
Agree with @AryeeteySolomonAryeetey. This is a bad answer.
6

EDIT: Look at Sludge's comment below.

EDIT 2: The issue in described at the bottom has been fixed in firebase-config 19.0.2.

Use Firebase Remote Config. Inside the Firebase console, inside the menu, scroll down to Grow and then Remote Config. Here you can add a parameter with a value. When you're done don't forget to publish the changes. It's kind of subtle.

enter image description here

Now install firebase_remote_config for Flutter.

After importing everything, you can retrieve your value using this code:

RemoteConfig remoteConfig = await RemoteConfig.instance;
await remoteConfig.fetch(expiration: Duration(hours: 1));
await remoteConfig.activateFetched();

remoteConfig.getValue('key').asString();

This way, the API key or token is never part of your application.

Note: there is currently an issue where you get a warning stating the application's name is not set, but this won't affect functionality.

8 Comments

I like this strategy. However, then you have documentation like pub.dev/packages/google_maps_flutter#getting-started that tells people to place the API KEY in AndroidManifest.xml or AppDelegate.m. Is this not bad practice? How should one replace this strategy?
Per Firebase: "Don't store confidential data in Remote Config parameter keys or parameter values. It is possible to decode any parameter keys or values stored in the Remote Config settings for your project." (firebase.google.com/docs/remote-config)
This is a bad answer, since in order to connect to Firebase project, you have to download a hard coded API_Key first. In other words, this approach is meaningless and misleading. firebase.google.com/docs/projects/api-keys#find-api-keys
@genericUser do you have a better solution?
Of course! :) Check out my answer stackoverflow.com/a/73455722/12695188
|
5

As mentioned, if the key is a secrete and you would like to protect it then simply do not put it in the client app. The app can be de-compiled and the key can be extracted for person willing to target your client.

I would delegate the task of communicating with this API to your Application Server. You can put the key in your server and have your server communicate with this external API and relay the response to the client.

Edit: Another approach, which is less secure but more convenient is to obfuscate your code using something like proguard. See this page for flutter instruction on android app: https://flutter.io/android-release/

3 Comments

I do not want to invest extra time building a back-end server for this, I did my research and found out how android handles this when building it with java :(stackoverflow.com/questions/14570989/…). There must be a way for flutter to handles this. Also here github.com/codepath/android_guides/wiki/…
But if you request the API from your own server everyone will be able to do that if they know the URI that they could again retrieve from your code.
This is a bad answer. There is not other option then saving the API Key hard-coded on the client side. That's the hole idea behind API keys.
3

The best practice should be to create a Firebase function, and then have that function use the secrets storage. This approach keeps the secrets out of your source code so there are no issues with sharing the code, and stores them securely. Firebase authenticates your app (and users if you use the login feature).

Secret Parameters

Call functions from your app

Comments

2

You should never put API keys into your client apps (unless they are marked as public keys), as mentioned above they are now compromised and could be used in malicious context. Additionally, it's generally a good idea to be able to roll-over api-keys, secret-keys periodically. Embedding them into your client apps just about kills that notion without some complex mechanism that in-itself will be an anti-pattern.

What we should be doing:

  1. Have an Authentication flow, where your client side inherits an identity of the end user, be it a custom email auth, sign-in with Provider auth (Google, Facebook)...
  2. If using a Provider, like Google Oauth2, don't put your Provider secret key in the client app (tutorials on this are generally not great. Only embed keys when the provider explicitly says you can), the code<=>token exchange should occur on the server-side when the secret key is necessary, or when you want to validate id_tokens.
  3. Send back to your client a Bearer token that represents their identity. This is all to limit scope of what secret is exposed to the end user. Bearer tokens must not contain sensitive information and must be signed (if your going to send back your own).
  4. Create a server-side implementation(s) for negotiation between API's and your Clients. Your Client app call your back-end API with their Identity Token, your back-end service call the API with secret and pass through results back to the client app.

As far as the backend, server-side impl. It can be as simple as a server-less function such as an AWS Lambda + API Gateway (1Million executions a month for free from Lambda). Or you can host larger, more complex service backends if needed.

Comments

0

We are using the Flutter DotEnv package for this.

Include this package in pubspec.yaml

flutter_dotenv: ^5.1.0

Create a file named .env in root folder. Register this .env file as a asset

  assets:
    - .env

Add loading of dotenv in your main function before loading the app void main() async { await dotenv.load(); }

Add the .env file in .gitignore file so it shouldn't be commited

Now add the required keys in the .env file, for e.g.

dev_firebase_remote_config_api_key=apikey_124433
sit_firebase_remote_config_api_key=apikey_1243734
uat_firebase_remote_config_api_key=apikey_1248262

These env config can be read in the code using.

import 'package:flutter_dotenv/flutter_dotenv.dart';

and now read the config

dotenv.env['dev_firebase_remote_config_api_key']!

Please refer Flutter DotEnv for more details

1 Comment

For not being uploaded to remote repository, yes. But for security, no. As stated on flutter_dotenv page: "Never store sensitive keys in the frontend". And i did try to reverse engineer a project with flutter_dotenv init, the result is .env file clearly showed without any encryption or any confusing things. Just .env with crucial data.
0

One effective way to securely prevent API keys in a Flutter project from being uploaded to a remote repository is by using the flutter_dotenv package. This package allows you to manage environment variables locally. You can find more details and usage instructions in the official documentation: [flutter_dotenv package](link to package).

2 Comments

As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.
For not being uploaded to remote repository, yes. But for security, no. As stated on flutter_dotenv page: "Never store sensitive keys in the frontend". And i did try to reverse engineer a project with flutter_dotenv init, the result is .env file clearly showed without any encryption or any confusing things. Just .env with crucial data.
-1

we should not store secret keys in a file because it is easy to decompile. You can try these methods

  • Fetch it from another source like firebase remote config
  • Hardcode key in a string with private accessible
  • Encrypt your key and store in a string and decrypt it whenever you need, don't store the decrypted key anywhere, just use getter function.

Comments

-4

You can use flutter_secure_storage from the oficial Flutter Packages

2 Comments

Possibly a stupid question, but when using this package, I will still rely on putting the keys in my source code, since they eventually have to be added to the secure storage at some point, so I can read them later. Or am I misunderstanding something here?
I think you understand it correctly. That package helps if you get secrets from your user and want to store them safely.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.