Removing Vulnerability From API Keys Using Serverless Functions

Imagine you need to develop the front-end of an APP that needs to get a list of the most popular movies from the MovieDB API.

Removing Vulnerability From API Keys Using Serverless Functions

Imagine you need to develop the front-end of an APP that needs to get a list of the most popular movies from the MovieDB API.

Let’s do it!

Go to MovieDB and signup to get your own API Key and follow along.

We will create a new project named protectingapisecrets using create-react-app and start coding our front-end.

npx create-react-app protectingapisecrets
cd protectingapisecrets
touch .env
npm install axios

Open this project with your favorite Code Editor, edit your .env file and add a variable with your API Key.

// .env

REACT_APP_API_KEY=<<your api key>>

next open your .gitignore file and add a line with your .env file and finally delete all files inside your src folder and create a clean i*ndex.js* App.js and App.css.

Start coding

// index.js

import React from 'react';
import ReactDOM from 'react-dom';

import App from './App';

ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// App.js

import React, { useState, useEffect } from "react"
import axios from "axios"
import "./App.css"

const App = () => {
const [movies, setMovies] = useState(null)

async function fetchMovies() {
const url = `https://api.themoviedb.org/3/movie/popular?api_key=${process.env.REACT_APP_API_KEY}&language=en-US&page=1`
const response = await axios.get(url)
const data = response.data.results
setMovies(data)
}

useEffect(() => {
fetchMovies()
}, [])

return (
<>
{movies === null ? (
<div className="loading">
<h2>Loading ...</h2>
</div>
) : (
<>
<div className="container">
{movies.map((movie) => (
<div className="movie" key={movie.id}>
<img src={`https://image.tmdb.org/t/p/w185/${movie.poster_path}`} alt={movie.title} />
</div>
))}
</div>
</>
)}
</>
)
}

export default App
// App.css

*,
*::after,
*::before {
margin: 0rem;
padding: 0rem;
box-sizing: inherit;
}

html {
font-size: 62.5%;
scroll-behavior: smooth;
}

body {
box-sizing: border-box;
background-color: #222831;
}

.loading {
padding-top: 5rem;
text-align: center;
}

.loading h2 {
color: white;
font-size: 2rem;
}

.container {
margin: auto;
padding: 2rem;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
max-width: 110rem;
grid-gap: 2rem;
}

.movie img {
width: 100%;
}

Cool, now let’s run

npm start

and check if everything is behaving as we expected

Deploying

We completed our front-end now it’s time to deploy it.

We will do this really easily and in just three steps with Netlify:

1st: Create a new GitHub repository and push your code.

2nd: Create an account on Netlify and connect your account to your GitHub.

3th: On your Netlify panel select “New Site from git” and chose the repository you created, you also need to check “show advanced” and add a new variable like this :

Click “Deploy Site” and that’s it, we now have a live version of our app!

The Problem

We stored our API Key in an environment variable to prevent it from being available on our code but if anyone opens the chrome dev tools while browsing your site, quickly can find your key.

soo, what can we do to protect our API key?

Serverless Functions

We can make a serverless function that handles our API call for us so we don’t have to publicly expose our key.

Let’s try it out, go back to your terminal, and run:

npm install netlify-lambda http-proxy-middleware env-cmd
mkdir functions
touch netlify.toml

Update scripts in your package.json file to look like this:

// package.json

"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"lambda-serve": "env-cmd netlify-lambda serve functions",
"lambda-build": "netlify-lambda build functions"
},

add this lines to netlify.toml file and add the functions folder to your .gitignorefile

// netlify.toml

[build]
functions = "lambda"

add a file named setupProxy.js to your src folder and past this code:

// setupProxy.js

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
app.use(
'/.netlify/functions/',
createProxyMiddleware({
target: 'http://localhost:9000',
"pathRewrite": {
"^/\\.netlify/functions": ""
}
})
);
};

This proxy setup will allow you to ping different endpoints depending on which environment you are if you are in development you want to ping localhost and in production you want the ./netlify/functions endpoint.

Coding our function

Let’s create a file named getMovies.js inside our functions directory

// getMovies.js

const axios = require("axios")

exports.handler = function(event, context, callback) {
const { API_KEY } = process.env

const url = `https://api.themoviedb.org/3/movie/popular?api_key=${API_KEY}&language=en-US&page=1`

const send = body => {
callback(null, {
statusCode: 200,
body: JSON.stringify(body)
})
}

const getMovies = async () => {
const response = await axios.get(url)
const data = response.data.results

send(data)
}

getMovies()
}

Now we need to edit our fetchMovies function inside App.js to use the serverless function instead of pinging the moviedb api directly:

async function fetchMovies() {
const url = `/.netlify/functions/getMovies`

const response = await axios.get(url)
const data = response.data
setMovies(data)
}

And finally, edit the .env file and change the name of the variable from REACT_APP_API_KEY to API_KEY

Great, let’s test it out!

Open two terminal windows and run npm start on the first and npm run lambda-serve on the second one and check your network tab.

Cool, we are calling the serverless function hiding the real endpoint of the api, let’s deploy it to Netlify, open your terminal and run:

git add .
git commit -m "finished version"
git push

When you push a commit to your GitHub repo Netlify will trigger a new deploy for your site. You just need to do one extra step and you are done, open your Netlify Panel and change the name of the environment variable you created on your fist deploy from REACT_APP_API_KEY to API_KEY

We are done, see you soon!

My deployed version here: https://protectingapisecrets.netlify.app/

npm install axios