Marcy Lab School Docs
  • Welcome
  • Student Guidelines & Policies
    • Student Handbook
    • AI Policy
    • Academic Calendar
  • Environment Setup
    • Local Environment Setup - Mac
    • Local Environment Setup - Windows
    • GitHub Setup
    • Postgres Setup
  • How-Tos
    • How To Code at Marcy: Code Style Guide
    • How to Do Short Response and Coding Assignments
    • How to Debug
    • How to PEDAC
    • How to Create A GitHub Organization and Scrumboard
    • How to Create Projects with Vite
    • How to Deploy on GitHub Pages
    • How to Deploy on Render
    • How to Test your API with Postman
  • Mod 0 - Command Line Interfaces, Git, and GitHub
    • Overview
    • 1. Command Line Interfaces
    • 2. Git & GitHub
    • 3. Git Pulling & Merging
    • 4. Git Branching & PRs
  • Mod 1 - JavaScript Fundamentals
    • Overview
    • 1. Intro to Programming
    • 2. Errors
    • 3. Node & Node Modules
    • 4. Variables, Functions & String Methods
    • 5. Control Flow, typeof, and Math
    • 6. Loops
    • 7. Arrays
    • 8. Objects
    • 9. Higher Order Functions: Callbacks
    • 10. Higher Order Functions: Array Methods
    • 11. Regex
  • Mod 2 - HTML, CSS & the DOM
    • Overview
    • 1. HTML
    • 2. CSS
    • 3. Accessibility (a11y)
    • 4. The Document Object Model (DOM) API
    • 5. Events
    • 6. Forms
    • 7. The Box Model and Positioning
    • 8. Flexbox
    • 9. Grid & Media Queries
    • 10. ESModules
    • 11. Vite
    • 12. LocalStorage
  • Mod 3 - Async & APIs
    • Overview
    • 1. Promises
    • 2. Fetch
    • 3. Building a Fetching App
    • 4. Async & Await
    • 5. A Generic Fetch Handler
  • Mod 4 - Project Week!
    • Important How Tos and Guides
      • How to Create a GitHub Organization and Scrum Board
      • How To Start a Project with Vite
      • How To Deploy a Project with GitHub Pages
    • Project Week Overview
    • Agile Methodologies
    • Deliverables & Milestones
    • Technical Requirements Checklist
    • Free API List
    • Collaborative GitHub
  • Mod 5 - Object-Oriented Programming
    • Overview
    • 1. Intro to OOP, Encapsulation, Factory Functions, and Closure
    • 2. Classes
    • 3. Private & Static
    • 4. UML Diagrams & Has Many/Belongs To Relationships
    • 5. Challenge: Implementing Has Many/Belongs To
    • 6. Inheritance
    • 7. Polymorphism
    • 8. Review and Practice
    • MDN: Object Prototypes
  • Mod 6 - Data Structures & Algorithms
    • Overview
    • Important How Tos and Guides
      • How to Debug
      • How to PEDAC
    • 1. Nodes & Linked Lists
    • 2. Singly & Doubly Linked Lists
    • 3. Stacks & Queues
    • 4. Recursion
    • 5. Trees
  • Mod 7 - React
    • Overview
    • Important How Tos and Guides
      • How to Create Projects with Vite
      • How to Deploy on GitHub Pages
    • 1. Intro to React
    • 2. Events, State, and Forms
    • 3. Fetching with useEffect
    • 4. React Router
    • 5. Building a Flashcards App
    • 6. React Context
    • 7. Global Context Pattern
  • Mod 8 - Backend
    • Overview
    • Important How Tos and Guides
      • How to Deploy on Render
      • How to Test your API with Postman
      • Postgres Setup
    • 1. Intro to Express
    • 2. Building a Static Web Server with Middleware
    • 3. Securing API Keys and Environment Variables
    • 4. RESTful CRUD API
    • 5. Model-View-Controller Architecture
    • 6. SQL and Databases
    • 7. JOIN (Association) SQL Queries
    • 8. Knex
    • 9. Your First Fullstack App!
    • 10. Migrations & Seeds
    • 11. Schema Design & Normalization
    • 12. Hashing Passwords with Bcrypt
    • 13. React Express Auth Template Overview
  • Mod 9 - Civic Tech Hackathon
    • Overview
    • Rubric
  • Mod 10 - Capstone
    • Overview
Powered by GitBook
On this page
  • Terms
  • Setup
  • Never Use API Keys In The Frontend
  • Create a Server Application To Make API Key Requests For You
  • API Requests From The Server
  • Environment Variables and Dotenv
  • Deploying with Environment Variables
  • Same Origin Requests from the Frontend
  • Development Frontend Request Proxy
  • Enable Proxy Requests In Development
  1. Mod 8 - Backend

3. Securing API Keys and Environment Variables

Previous2. Building a Static Web Server with MiddlewareNext4. RESTful CRUD API

Last updated 1 month ago

Follow along with code examples !

Thus far, we have not been able to deploy a project that uses an API key without likely exposing that API key to the public. Now that we have the power to build a server, we can finally do just that!

Table of Contents:

Terms

  • 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 domain to another domain, protocol or port.

  • Request Proxy In Development — faking the origin of the request in a frontend development server to match the origin of the backend server

Setup

For this lecture, you'll need an API that requires an API key - a secret code that verifies your identity as a developer using an API's limited resources.

We'll use the endpoint below to access the top stories in the "Arts" section:

https://api.nytimes.com/svc/topstories/v2/arts.json?api-key=yourkey

Once you have an API key, clone the practice repository linked above and do the following:

  • cd into the frontend application and install dependencies.

  • Then, create a file inside of frontend/ called secrets.js and paste the following code:

    export const API_KEY = "paste-api-key-here"

    This value is imported and used by the frontend/src/adapters/nytAdapters.js file to send a request to the NYT API. The file is already added to .gitignore.

  • Finally, run the program with npm run dev. You should see the application below:

Never Use API Keys In The Frontend

Working with API keys presents a tricky problem. We need the API key to exist in our program to access the API's resources, but we need to avoid putting it in a place that can be viewed by the public.

Q: Why is it not a good idea to share your API key? What really could go wrong?

The API key is a way to verify your identity as a developer. Some APIs will charge you for each request that you make using your API key and if someone else gets a hold of your API key, they could steal your request resources.

There are two common places that we can mistakenly expose our API keys to the public.

  1. In a public remote repository (on GitHub)

  2. In the HTTP requests sent by the client (the browser) to the API

To avoid the first mistake, we should always store API keys and other sensitive information in a git-ignored file (e.g. secrets.js or .env).

To avoid the second mistake, we must NEVER send requests with API keys from client-side (frontend) applications.

NEVER send requests with API keys from client-side (frontend) applications!

To understand why, run the frontend application from the provided repo, view the Networks tab and refresh the page. Then, look through the requests sent by the application and see if you can find the exposed API key!

All requests sent by the client will appear in this Network tab. Even if the API key is hidden in a gitignored file or stored in an environment variable (more on that soon), the client-side (frontend) application still needs to embed that value into the HTTP request URL. There is simply no way to hide it from this Networks tab.

Create a Server Application To Make API Key Requests For You

Generally, client-side (frontend) code is inherently insecure because, well, we just give the code to the user to run on their browser! If we're not careful, we may accidentally give away sensitive data.

Server-side (backend) code on the other hand is much more secure. A client can send HTTP requests to a server's endpoints to request the server to execute code, but the client has no visibility into the inner-workings of the server.

So, to securely use an API key, we must use it in our server-side code. This means building a server to make requests on behalf of the client, acting as a sort of middleman. As long as the server has the API key, the client doesn't need it!

In other words, the client sends a request to the server without any API key. The server then sends a request using the API key and sends the fetched data back to the client.

In order to implement this, we need to build a server application that:

  • Has its own API endpoint that the client can use without the client needing to know the API key.

  • Securely stores the API key (we'll use environment variables for this)

API Requests From The Server

Let's start by making a server endpoint that fetches from the API using the API key.

// We'll secure this value soon!
const API_KEY = 'paste-your-api-key-here';

// First, we make a controller
const serveTopArtStories = async (req, res, next) => {
  const url = `https://api.nytimes.com/svc/topstories/v2/arts.json?api-key=${API_KEY}`;

  try {
    // This is pretty standard fetching logic
    const response = await fetch(url);
    const data = await response.json();
    const storiesWithTitle = data.results.filter(story => story.title);

    // send the fetched data to the client
    res.send(storiesWithTitle);
  } catch (error) {
    // or send an error. 503 means the service is unavailable
    res.status(503).send(error);
  }
}

// Then, we make that controller available with an endpoint
app.get('/api/stories', serveTopArtStories)

There is one thing we need to clean up first — we need to secure the API key.

Environment Variables and Dotenv

The most common way to store sensitive server-side data like API keys is with a .env file ("dot E-N-V file").

.env files have a really simple format.

secretValue1="abc123"
anotherSecret="my password"
API_KEY="xyz"

Remember to add .env to your .gitignore file!

To use the environment variables in our server code, we'll use the dotenv module from npm.

npm i dotenv

dotenv provides a dotenv.config() method which looks for .env files and loads them into a special object process.env object:

const dotenv = require('dotenv');
dotenv.config();

// .env values are added to process.env
console.log(process.env.API_KEY); // xyz

With our API_KEY variable moved to the .env file, we can modify our controller:

const serveTopArtStories = async (req, res, next) => {
  const url = `https://api.nytimes.com/svc/topstories/v2/arts.json?api-key=${process.env.API_KEY}`;

  try {
    const response = await fetch(url);
    const data = await response.json();
    const storiesWithTitle = data.results.filter(story => story.title);
    res.send(storiesWithTitle);
  } catch (error) {
    console.error("Error while fetching:", error);
    res.status(503).send(error);
  }
}

Deploying with Environment Variables

.env files are so widely used that most server hosting services will provide a way to securely upload .env files. That way, your deployed server will have access to .env values without needing those values to be stored on GitHub.

For example, on Render, you can add environment variables when configuring your new web service.

Same Origin Requests from the Frontend

Now our server can perform an API request using a protected API key.

Let's update the frontend React application to use our server instead of directly accessing the NYT API.

Let's update the url that our adapter function uses and remove the filter code which is now handled server-side:

import { handleFetch } from './handleFetch';

export const getTopStories = async () => {
  const url = `/api/stories`;

  return await handleFetch(url);
}

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.

Why is the API url just /api/stories and not http://localhost:8080/api/stories?

In this case, we visit http://localhost:8080 to access both the client-side (frontend) code and the API endpoint http://localhost:8080/api/stories. In this case, the client-side code is coming from the same origin as the destination of its API request. When we leave out the host (http://localhost:8080), our browser assumes we are making a same-origin request.

Development Frontend Request Proxy

Re-building the frontend application each time we make a change is tedious. And remember, we have a frontend development server that will automatically re-load the application each time we save a file! However, when we try running the frontend with npm run dev, the app breaks!

But remember, the API lives at port 8080. Now that we're viewing the frontend from port 5173, we've changed the origin of the request!

Enable Proxy Requests In Development

The word "proxy" is synonymous with the word "substitute".

To set up proxying, replace your frontend/vite.config.js file's contents with the code below:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

const SERVER_PORT = 8080;

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      '/api': {
        target: `http://localhost:${SERVER_PORT}`,
        changeOrigin: true,
      },
    },
  },
});

Now, when we run our development server, all requests starting with /api will be sent to the defined target which we set to http://localhost:8080.

We'll use the New York Times API because the API is quite friendly to use. Make an account on the and follow the instructions to enable the "Top Stories API".

Start your server and visit to see the fetched data! Note that the client (your browser) doesn't need an API key anymore to access this data!

Visit the server to see the updated frontend!

When we send requests to servers that we don't control, we include the full URL (e.g. ). These kinds of requests are cross-origin because the origin of the request and the destination are not the same.

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 .

When the frontend makes a request to /api/gifs from , the browser assumes it is a same-origin request and sends the request to .

To enable the development version of our frontend to send requests to http://localhost:8080, we need to redirect the "same-origin" requests from to . This is called "proxying".

NYT Developers Page
http://localhost:8080/api/stories
http://localhost:8080
https://dog.ceo/api/breeds/image/random
http://localhost:8080
http://localhost:5173/
http://localhost:5173/api/gifs
http://localhost:5173
http://localhost:8080
here
Terms
Setup
Never Use API Keys In The Frontend
Create a Server Application To Make API Key Requests For You
API Requests From The Server
Environment Variables and Dotenv
Deploying with Environment Variables
Same Origin Requests from the Frontend
Development Frontend Request Proxy
Enable Proxy Requests In Development
An app using the NYT API to show top stories in the Arts section
The Network tab can expose API Keys used by the client-side (frontend) application.
The client sends a request to the server without any API key. The server then sends a request using the API key and sends the fetched data back to the client.
Add environment variables when configuring render web servces
The client sends a request to the server without any API key. The server then sends a request using the API key and sends the fetched data back to the client.