loading...

Firebase Functions: React users need to stop storing sensitive API keys in .env files!

andersjr1984 profile image andersjr1984 Updated on ・5 min read

This is a tutorial from my digital portfolio/blog. Please stop by and take a look, then criticize me in the comments. Haha.

Many of you are reading this because you are an awesome Front-End Developer with a sick idea, but don’t quite know how to secure some of the necessary API calls without a full back-end environment. You hop online and everyone is telling you to put your API keys in an .env file then blah blah blah and rest easy. Well, if you have sensitive API keys and you follow the .env advice, you are putting yourself at a high risk.

Here is why a .env file doesn’t protect you in production:
The background is that when React creates the production build, it jumbles everything up into a chunk.js file. If you have a react project with some API keys, do me a favor and go to the page, then inspect it using the DEV tools. Go to Sources, then go to the static folder, then go to the js folder. There is a weird filename that you probably don’t recognize that ends with chunk.js. Open that file up and copy the contents to a word processor (I tried searching in the inspect tool, but it was suuuuper slow). Go to your sacred .env file and copy one of your “secure” API keys, then jump back into your word processor. Try a find on your API key. What is that you say, your key is in this PUBLIC file?

Well, you have some work ahead of you, but fret not, you can use Google Firebase to act as a back-end server and save your back-end from API fraud and ending up like this guy. I’ll line out how I did it and if I can do it, you should be able to. You do need to be on the Blaze plan for this to work. If you make a lot of external API calls in a month, you could end up paying.

  1. Start a Firebase account, then create a project for this little activity. I trust you can figure this out using google.
  2. Install Firebase tools (npm install -g firebase-tools), login (firebase login) and initiate the functions while you are in your project's folder (firebase init functions). Follow the instructions.
  3. Now you have a nifty little folder in your app that holds your functions and an index.js file that holds the first function you will be using. Just uncomment the helloWorld function! But ugh, what kind of weird language is that written in?
  4. Hello World
  5. We are going to have to make this work in React. First off, you are going to have to make it an onCall function if you are using React and you can't send your response back using response.send. . . This needs a return:
  6. Hello World React
  7. Create a firebase.js file in your src directory. Secure the config information in your precious .env file so it provides a little difficulty to steal. And now you are thinking that I just told you that your API Keys need to be secure. The Firebase API is slightly different. If you are using secure API Keys, listen to this man on how to secure them!
  8. Firebase File
  9. Find an inconspicuous place in one of your exported components and create a variable that points to your firebase function. You can then call your function using this variable, then log the value to the console. The data comes in as an object, so you will have to dig one step deeper, into the 'data' key.
  10. Calling the Function
  11. Push your functions to firebase using the command firebase deploy --only functions.
  12. The results are in!
  13. Hello!
  14. Firebase currently uses Node.js 6, which is a pain when trying to write async/await functions. These are typically much easier to understand than promise.then functions for many of the junior developers I talk to. Fortunately, you can make firebase accept Node.js 8 as its master opening your package.json file in your Functions folder and adding the following line:
  15. Gimmee async/await
  16. Write your API function in the index.js of your Function folder: I'm not going to go through all the trials I had in writing my API call to Yelp. I'm just going to give you the code and you can reapply it to most API calls that use Headers. If you are a master at React API calls, you'll notice Authorization is in quotes. Took me a long time to figure that out. You'll see that my API call only returns tacos, that is because I think tacos are the only thing that are important. You'll have to adjust your req call to add additional variables.
  17. I only want tacos
  18. What does the following line mean? functions.config().yelp.key calls your API key from the config file of your functions library. You save it by using your command prompt in the functions folder and typing firebase functions:config:set someservice.key="THE API KEY" someservice.id="THE CLIENT ID".
  19. Get out your console.log hat and test stuff out till you get something similar to the following code to work. Also, aren't async/await functions beautiful.
  20. I still only want tacos

I know, you are blown away and hungry for tacos. Feel free to comment or send me an e-mail or give me a job.

Posted on by:

andersjr1984 profile

andersjr1984

@andersjr1984

Frontend Developer for Spire Digital.

Discussion

markdown guide
 

This is actually quite misleading. While it is possible to grab your API key from your site, it is 100% safe to leave this key in the open. It is a public key and the tutorials instruct you to do so.

The way to actually secure your real-time database or Firestore on Firebase is to set rules for accessing your database based on information such as request.userId or whether or not they've been granted access to a group. If you don't do this your server is very much still open to attack.

This being said, to do more complicated manipulation of your database, you should use Firebase functions, not because of your API key, but rather, because exposing your business logic to the world is dangerous and could be easily manipulated to provide different, potentially harmful, results.

 

I am talking about API keys that are meant to be private. I even discuss how the Firebase API key and some other publishable API keys are perfectly fine in client-side code. There are a couple of links in the post that justify leaving the Firebase API key on client-side code.

 

It would be very helpful if you added that this information is straight from the docs. The link is here: firebase.google.com/docs/functions...

 

Hmmm. That is only a very small part of the tutorial above, it doesn't hit on how to make any of these functions work in React for people who may be new to the tools. Really, all that tells you how to do is set your environmental variables.

 

Grabbing API Keys from code hosted publicly shouldn't be an issue. Your rules should guard your databases (Firestore, Firestorage), CORS should guard Auth and Functions.

 

Lovely!

Heads-up, your portfolio link is broken.

 

Blah blah blah... Don't use client tech to secure yourself. Use client tech with vendor lock in.

Edit: imho you should use a separate server based application and pass a short lived token through to your live app. This doesn't need to be something with vendor lock in. The client app I'm working on now gets 3 values injected at deploy. LoginUtl. LogoutUrl and optionally manage users url.

It's meant to be integrated into different systems and works fine standing alone. The token after auth is sent with every API request and no secrets leaped to the client build.

An API gateway, imho, is the best way to roll

 

Awesome reply! I'll have to look into that. I know this may be common knowledge to most, but there is some confusing information out there. Some of the common google search returns still suggest using .env files and if you don't read carefully, you may be inclined to use .env when deploying your react app. I wanted to find out why this isn't safe and tried to clearly explain it.

This is an aggregation of several different sources that I used to make a couple API calls. Having all this information in one place will hopefully make someone's life a littler easier, I'm still a newb and I would have loved to be able to follow something like this from the beginning.

 

I can understand that... I actually do use .env files, but the front end only uses them for the build/dev server to inject the 3 values... for the most part, I only use them for dev, and in the .gitignore so they don't get added. I then inject the necessary environment variables into the CI/CD for build and/or deploy environments. My front end usually has a limited number of them.

And you are right, it's too easy to accidentally inject unexpected values into a build. If you stick to just what you intend to inject such as with process.env.VARNAME in the webpack config, or in a separately loaded JS file that gets injected into the client.