Securing API Keys with Environment Variables
Last updated
Last updated
Serving static assets is a core functionality of a server application.
The second use case for a server is to perform 3rd-party API requests that require an API key in a manner that protects the developer's API key.
Let's do it!
API Key - a secret code that verifies your identity as a developer using an API's limited resources. Do not share these!
Environment Variable — a hidden variable stored on the host's machine (your laptop or Render.com) and accessible in Node through the process.env
object
.env
file - a file to store hidden variables like API keys. Ignored by GitHub and uploaded to Render for deployment.
dotenv
module - an npm package for importing .env
files
Cross-origin requests - HTTP requests made from one origin to another.
Request Proxy In Development — faking the origin of the request in a frontend development server to match the origin of the backend server
Fork this repo
cd
into the server
folder and run npm i
Start the server application with nodemon index.js
In a separate terminal, cd
into the frontend
and run npm i
Start the frontend application with npm run dev
Note that there is a utils/fetchData
helper function provided for you in both the server
and the frontend
.
For this lecture, you'll need an API key - a secret code that verifies your identity as a developer using an API's limited resources. Do not share these!
We'll use the Giphy API because the API is quite friendly to use.
Then:
Go to your dashboard
Create an App.
Select API (it is free) and give your app a name (e.g. "giphy search") and confirm to get your API key.
Copy the API key
Open the frontend/src/App.jsx
file and paste your API key into the string assigned to API_KEY
If your frontend folder is running, you should see the top 3 trending gifs appear.
If we want to avoid making our API keys public, we need to be careful with how we use them in our code. Our frontend can't safely make requests using the API key — anyone using our deployed application can just look at the Network tab to see the API keys in the request URL.
So, we have to make the requests using the API keys in our backend.
But even to deploy our server code, we'll need to store the API key on GitHub. Or do we?
Environment Variables are hidden variable stored in a .env
file that lives on the host (your laptop or a deployment host like Render)
They are great for storing API keys:
We can import the environment variables from .env
using the dotenv
module from npm.
In our server JavaScript, we can write:
With this endpoint set up, visit http://localhost:8080/api/gifs and see the fetched data! Again, the client has no vision of the API key!
The beauty of environment variables is that we can hide them from our GitHub repository using a .gitignore
file and then upload them directly to the deployment host (e.g. Render).
Now our server can perform a 3rd-party API request using a protected API key. However, we still have a frontend React application that is using the Giphy API directly and exposing our API key!
Instead of sending the fetch request to the Giphy API, the frontend should send the request to the server.
Normally when sending requests to 3rd-party APIs, we include the full URL: http://someapi.com/endpoint
. These kinds of requests are cross-origin requests because the origin (http://someapi.com
) is different from the origin of our server (http://localhost
or wherever the app is deployed). Our server can safely do this but as we've seen, our front-ends should not.
However, we want to send a same-origin request — the origin of the request is the same as the origin of the response (we got the frontend application from the same server that we are now requesting data from).
So, our request URL will be: /api/gifs
. When we leave the host out of the URL, our browser will assume the request is a same-origin request.
To test this out we should:
Re-run npm run build
to re-build our frontend application's static assets in the dist/
folder.
Re-run the server which will serve our updated frontend static assets.
Visit the server http://localhost:8080 to see the updated frontend!
tldr: copy and paste the code snippet below into your
vite.config.js
file to make your development frontend application play nicely with your server and avoid cross-origin resource sharing (CORS) issues.
We could totally stop here and everything would be great. We can now build server applications that provide frontends that utilize 3rd-party APIs without exposing API keys by utilizing environment variables!
However, we need to remember that we have two different versions of our frontend:
The development version (which lives in the frontend/src
folder)
The built "production/distribution" version (which lives in the frontend/dist
folder)
This is where things may get confusing... Bear with me...
In that last step, we tested the frontend changes by running npm run build
to update our frontend dist
folder, and by opening the static frontend application served by our backend at http://localhost:8080.
If we instead tested the frontend changes by running npm run dev
and viewing the app through the Vite development server, the app would break. In fact, if you do that now, it will still break. This is because npm run dev
will serve our frontend at http://localhost:5173/ which is a different origin from our server: http://localhost:8080.
When the frontend makes a request to /api/gifs
from http://localhost:5173/, the request is going to http://localhost:5173/api/gifs which doesn't provide the resources we are looking for.
To enable the development version of our frontend to send same-origin requests, we need to "trick" the Vite development server into sending "same-origin" requests to http://localhost:8080 instead of http://localhost:5173.
To do this, copy and paste the code below into your frontend/vite.config.js
file. This is called a proxy.
If you haven't already, make an account on the