How to secure API keys in Flutter
Hello everyone, it’s been a while since I wrote my last article. I felt like it ‘s time to write something new and useful. So, here we are. You can check out my previous articles below.
Some of you might not be aware of that but almost anything bundled in your app’s build can be demystified. It includes your commented test account credentials, funny todos, a grocery list that you committed by mistake, and your API keys. Yes, some of you still store groceries in your projects.
What would be a better move?
We can use .env
files to store our secrets. And then we can access them easily. Check out flutter_dotenv for more. This approach is better but it is much easier to demystify secrets. Why? Because we have to add .env
files to pubspec.yaml
as assets to access them in dart files. Why is it a bad thing? Because assets can be reached easily without any decryption or other fancy reverse-engineering magics.
You can simply use Android Studio’s APK analyzer to see each resource even if — obfuscate
flag is used, assets can still be accessible easily. So, your secrets are not safe.
What would be a smarter move?
Well, have you ever heard about environment variables? Yes, I am talking about them. You can pass critical information such as your anniversary through environment variables. Add — dart-define
into your toolArgs
of you launch.json
file in VS Code. And then access them with String.fromEnvironment('NAME');
print(const String.fromEnvironment('ANNIVERSARY'));
You can do the same in Android Studio too.
So, did we solve the problem? Not so fast. This is a good starting point and it will help you to secure your secrets. But it is not a best practice since you give everything through your run configurations. Of course, there are workarounds like passing all the data through a file with — dart-define-from-file
. And you can add it .gitignore
and you are good to go. But hear me out. There is a better way.
Meet our rockstar: Envied
Envied is the best option so far for ME.
Why?
It is safe because we don’t add .env
files as assets. It is safe because it generates dart files which include your credentials in base64 format which makes it harder to decompile and find the secrets. Of course, you need to use obfuscate=true
to enable obfuscation.
It is officially supported to use different flavors but I will show you how to do it in the right way.
Let’s create a blueprint for both flavors.
Remembered String.fromEnvironment
? We give ENV
key to define which flavor we want to use. And AppSecret
will return that flavor’s secrets.
Let’s see inside of ProductionSecret
. First, we need to add part
to generate secrets from the environment file. We need to use @Envied
annotation with the path of the environment and obfuscate=true
.
We implement AppSecret and AppEnvFields, therefore, we agree with what keys should be overriding and AppSecret has exact same secret. So, we can use them without breaking anything in different flavors.
Due to obfuscate=true
each key should be final
. And each should have @EnviedField
with the secret’s key name in the environment file. Which is SECRET_KEY
in our case.
Then we say _ProductionSecret.secretKey
which will come from the generated file.
Lastly, we need to run one of the below commands.
# dart
dart run build_runner build
# flutter
flutter pub run build_runner build
Do not forget to add .env
, production_secret.dart
and production_secret.g.dart
into .gitignore
. You don’t want to store these in your repository.
How to use secrets in CI/CD flows?
If you use a CI/CD tool such as Github Actions or CodeMagic, you need to store your keys in these platforms and dynamically generate envied files in the workflow. You can check out the below link to see how to do it in CodeMagic.
Conclusion
Well, that’s pretty much it. I hope you learned something useful today. Still, I do not recommend you to store the nuclear codes with envied just in case.
Don’t forget to follow me, give claps, and share the article if you found it helpful.
Thank you for reading.