Photo Credit: Mariko Margetson
This article is not really a tutorial or even the answer to a question. In a way it is a question itself.
Here I explain the problem I was having and how I solved it up to this point, but I'm almost 100% certain that I'm missing some details and understanding.
My hope is that the article might start some arguments, dialogues and conversations and maybe I'll get more context and information about better ways of solving the problem.
Assumption:
I'm assuming you are generally familiar with Firebase, Firebase Emulators, and Firebase Functions. If not, let me know and I'll see if I can help. I need to write an article about that whole journey anyway.
Context:
An app I'm working on had some API keys hard-coded in it. I wanted to hide this information, but the application needs some of these keys on start up.
Problem:
This Flutter app is deployed with Firebase Hosting. The Hosting environment does not have environmental variables. So how do I set and get the environmental variables the app needs without hard-coding and committing keys and secrets?
I followed (as far as I understood it) the advice given by Frank van Puffelen on Stack Overflow.
I know that there are subtlties in his advice that I'm not getting, especially around his comment "If you want them to remain secret, it's not just about not having them in the code, it's about not using them from the client at all." I still don't understand that statement.
Solution:
In my functions
directory I created a .runtimeconfig.json
file (and added it to my .gitignore
) with the various keys I needed. This is the local development way of setting environmental variables for Firebase Functions.
Then I wrote an HTTPS
cloud function called getEnv
. This function returns the Functions functions.config()
object.
Now I am able to get the keys and secrets I need to start the app by fetching the config object from the getEnv
function as the app is starting up.
Final code:
In functions/.runtimeconfig.json
:
{
"algolia": {
"appid": "ID",
"apikey": "KEY"
},
"webmerge": {
"key": "KEY",
"secret": "SECRET",
"stashkey": "STASH_KEY"
},
}
In functions/index.ts
:
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
. . .
const cors = require('cors')({origin: true});
const envObj = functions.config();
. . .
admin.initializeApp();
export const getEnv = functions.https.onRequest((req, resp) => {
cors(req, resp, () => resp.status(200).send(JSON.stringify(envObj)));
});
. . .
NOTE: I used the cors
package to get around CORS errors when working locally. I would get these errors when localhost:5000
(Emulator hosting) called localhost:5001
(Emulator functions).
In web_flutter/main.dart
:
Future<Map<String, dynamic>> fetchEnv(String functionsURL) async {
var response = await http.get('${functionsURL}/getEnv');
return json.decode(response.body);
}
Future<void> main() async {
try {
var functionsURL = 'FUNCTIONS_URL';
var app = fb.initializeApp(firebase app details);
if (window.location.hostname == 'localhost') {
app.firestore().settings(Settings(
host: 'localhost:8080',
ssl: false,
));
functionsURL = 'http://localhost:5001';
}
var env = await fetchEnv(functionsURL);
var searchClient = Algolia.init(
applicationId: env['algolia']['appid'],
apiKey: env['algolia']['apikey']);
runApp(MyApp(
repository: Repository(app.firestore(), searchClient),
authentication: Authentication(app.auth())));
} on fb.FirebaseJsNotLoadedException catch (e) {
print(e);
}
}
Once I confirmed that this was working locally, I was able to use firebase functions:config:set
to set this data in the live Functions environment and deploy my updated hosting and functions with firebase deploy
.
Your Thoughts?
I'd love to hear your thoughts and experience with environmental variables in general and specific to Firebase. Is there a better way to do this?
Thanks for reading!
Top comments (0)