Securing Netlify Functions with serverless-jwt and Auth0
Tags: netlify, jamstack, jwt, gatsby
Published: 2020-07-28 15:47:14

---
title: Securing Netlify Functions with serverless-jwt and Auth0
published: true
date: 2020-07-28 00:00:00 UTC
tags: netlify, jamstack, jwt, gatsby
canonical_url: https://sandrino.dev/blog/securing-netlify-functions-with-serverless-jwt
---

> Want to skip the details? Try out the [online demo](https://gatsby-auth0-netlify-functions.netlify.app/).

## Introduction

With the rise of [JAMstack](https://jamstack.wtf/) we've seen a lot of frameworks and platforms offer the ability to build and host Serverless functions as lightweight backends for your applications.

In the last few years the Node.js ecosystem has provided many solutions to handle authentication in your web applications through libraries like [Passport.js](http://www.passportjs.org/) and [express-jwt](https://github.com/auth0/express-jwt/). But due to the different programming model of Serverless functions (lambdas as opposed to full blown web servers) these libraries are not the perfect tool for the job.

This is where [serverless-jwt](https://github.com/sandrinodimattia/serverless-jwt) comes in: a simple and lightweight solution focused at solving authentication for your Serverless functions. Here's an example of what it would take to protect a Netlify Function:

```js
const { NetlifyJwtVerifier } = require('@serverless-jwt/netlify');

const verifyJwt = NetlifyJwtVerifier({
  issuer: 'https://sandrino.auth0.com/',
  audience: 'urn:my-api'
});

exports.handler = verifyJwt(async (event, context) => {
  const { claims } = context.identityContext;
  return {
    statusCode: 200,
    body: `Hi there ${claims.email}!`
  };
});
```

Let's take a look at how we can use this library to secure Netlify Functions: we're going to create a very simple Gatsby application which will authenticate users with Auth0 and then interact with Netlify Functions.

![Netlify Functions](https://sandrino.dev/assets/blog/securing-netlify-functions-with-serverless-jwt/banner.jpeg)

## Configuring Auth0

Before we can build our application we're going to configure our Auth0 account with 2 new entities:

- A **Application** representing the Gatsby application
- An **API** representing the API we'll build in Netlify Functions

### Configuring the Gatsby client

In the **Applications** section we'll create a new **Application**:

- Application Type: **Single Page Application**
- Allowed Callback URL: **http://localhost:8888**
- Allowed CORS: **http://localhost:8888**
- Allowed Web Origins: **http://localhost:8888**
- Allowed Logout URL: **http://localhost:8888**

![Create application in Auth0](https://sandrino.dev/assets/blog/securing-netlify-functions-with-serverless-jwt/create-app.jpeg)

### Configuring the API

In the **APIs** section we'll create a new **API** representing our backend. In this example we'll create a simple TV shows API so I'm filling in the following settings:

- Name: **TV Shows**
- Identifier: **https://api/tv-shows**

![Create API in Auth0](https://sandrino.dev/assets/blog/securing-netlify-functions-with-serverless-jwt/create-api.jpeg)

> It doesn't really matter what you choose as the **Identifier**, this will simply be the identifier or audience of your API.

We'll also create a few scopes in this API. Later in the implementation we'll enforce the presence of certain scopes to restrict access to certain functions. Applications will be able to request the `read:shows` and `create:shows` scopes.

![Create scopes in Auth0](https://sandrino.dev/assets/blog/securing-netlify-functions-with-serverless-jwt/create-scopes.jpeg)

## Creating an API using Netlify Functions

So we'll create 2 functions which can be called from the Gatsby application:

- `/.netlify/functions/me`: This function will require the user to be authenticated and simply return the current user information.
- `/.netlify/functions/shows`: In addition to being authenticated, this function will also require the `read:shows` scope to be available.

To start we'll create a file containing all of the authentication logic using `@serverless-jwt/netlify`. This will create a JWT verifier for Netlify which we can use to secure our functions.

```js
const { NetlifyJwtVerifier, removeNamespaces, claimToArray } = require('@serverless-jwt/netlify');

const verifyJwt = NetlifyJwtVerifier({
  issuer: process.env.JWT_ISSUER,
  audience: process.env.JWT_AUDIENCE,
  mapClaims: (claims) => {
    const user = claims;
    user.scope = claimToArray(user.scope);
    return user;
  }
});

/**
 * Require the request to be authenticated.
 */
module.exports.requireAuth = verifyJwt;
```

Let's also go ahead and create a `.env` file containing the issuer and audience:

- `JWT_ISSUER`: Your Auth0 (Custom) Domain
- `JWT_AUDIENCE`: The identifier of the API you created

```bash:.env
JWT_ISSUER=https://sandrino.auth0.com/
JWT_AUDIENCE=https://api/tv-shows
```

And that's it. We can now import the verifier and secure our handler:

```js
const { requireAuth } = require('../../lib/auth');

exports.handler = requireAuth(async (event, context) => {
  try {
    const { claims } = context.identityContext;

    return {
      statusCode: 200,
      body: JSON.stringify({ claims })
    };
  } catch (err) {
    return {
      statusCode: 500,
      body: JSON.stringify({ error_description: err.message })
    };
  }
});
```

This handler will now only execute if a valid token has been provided, otherwise it will fail and return an error message to the user. Let's go ahead and build a Gatsby application to test this out.

## Building the Gatsby application

I will not go into too much detail on how to build the Gatsby application, since there's plenty of resources available covering this topic in depth. Now for this example to work we'll need to use [Auth0's React SDK](https://github.com/auth0/auth0-react):

```js
import React from 'react';
import { navigate } from 'gatsby';
import { Auth0Provider } from '@auth0/auth0-react';

import './src/styles/site.css';

const onRedirectCallback = (appState) => navigate(appState?.returnTo || '/');

export const wrapRootElement = ({ element }) => {
  return (
    <Auth0Provider
      domain={process.env.GATSBY_AUTH0_DOMAIN}
      clientId={process.env.GATSBY_AUTH0_CLIENT_ID}
      audience={process.env.GATSBY_AUTH0_AUDIENCE}
      scope={process.env.GATSBY_AUTH0_SCOPE}
      redirectUri={window.location.origin}
      onRedirectCallback={onRedirectCallback}
    >
      {element}
    </Auth0Provider>
  );
};
```

The environment variables come from my `.env` file. The environment variables are prefixed with `GATSBY_*`, making them available in the browser. Fill in the following settings:

- `GATSBY_AUTH0_DOMAIN`: Your Auth0 (Custom) Domain
- `GATSBY_AUTH0_CLIENT_ID`: The Client ID of your Gatsby application
- `GATSBY_AUTH0_AUDIENCE`: The identifier of the API you created
- `GATSBY_AUTH0_SCOPE`: The `openid profile` scopes are used for authentication while the `read:shows` scope will be present in the `access_token` which we'll provide to Netlify Functions.

```bash
GATSBY_AUTH0_DOMAIN=sandrino.auth0.com
GATSBY_AUTH0_CLIENT_ID=6qy0GxNC46tKvoYPc60mlW6X55jyrlaY
GATSBY_AUTH0_AUDIENCE=https://api/tv-shows
GATSBY_AUTH0_SCOPE=openid profile read:shows
```

Now that the Gatsby application has been configured we'll add a Login button to the header:

```js
import React from 'react';
import { useAuth0 } from '@auth0/auth0-react';

export default () => {
  const { loginWithRedirect } = useAuth0();

  return (
    <header>
      <div>
        <span className="text-xl">Gatsby &amp; Netlify Functions</span>
        <button onClick={() => loginWithRedirect()}>Login</button>
      </div>
    </header>
  );
};
```

Clicking this button will redirect users to Auth0 where they can authenticate. Once they are authenticated they can call the API:

```js
export default function Home() {
  const [response, setResponse] = useState();
  const { getAccessTokenSilently, isLoading, error, user } = useAuth0();

  const getUserProfile = async () => {
    try {
      setResponse('Loading...');

      const token = await getAccessTokenSilently({
        audience: process.env.GATSBY_AUTH0_AUDIENCE
      });

      const api = await fetch('/.netlify/functions/me', {
        headers: {
          authorization: 'Bearer ' + token
        }
      });

      const body = await api.json();
      setResponse({
        status: api.status,
        statusText: api.statusText,
        body
      });
    } catch (e) {
      setResponse(e.message);
    }
  };

  return <Layout>...</Layout>;
}
```

We'll first use the Auth0 React SDK to acquire an `access_token` using the `getAccessTokenSilently` method, which can then be presented to our first function. If the token is valid, the function will return the user and it will be rendered on the page.

## Implementing Authorization

Let's now also add a simple authorization use case to our example. If you want to list the TV Shows that are available you might need to have a subscription or a specific role.

So we'll implement a helper which will require a scope to be present when a call is made to a function:

```js
module.exports.requireScope = (scope, handler) =>
  verifyJwt(async (event, context, cb) => {
    const { claims } = context.identityContext;

    // Require the token to contain a specific scope.
    if (!claims || !claims.scope || claims.scope.indexOf(scope) === -1) {
      return json(403, {
        error: 'access_denied',
        error_description: `Token does not contain the required '${scope}' scope`
      });
    }

    // Continue.
    return handler(event, context, cb);
  });
```

If a valid token is presented, this will now require the existence of a given scope. And we can use this to implement our TV Shows endpoint. For this function, the user must present a token with a `read:shows` claim.

```js
const fetch = require('node-fetch');
const { requireScope } = require('../../lib/auth');

exports.handler = requireScope('read:shows', async (event, context) => {
  try {
    const res = await fetch('https://api.tvmaze.com/shows');
    const shows = await res.json();

    return {
      statusCode: 200,
      body: JSON.stringify(
        shows.map((s) => ({
          id: s.id,
          url: s.url,
          name: s.name
        }))
      )
    };
  } catch (err) {
    return {
      statusCode: 500,
      body: JSON.stringify({ error_description: err.message })
    };
  }
});
```

By default Auth0 will allow applications to request any of the available scopes on an API. But we can change this by flipping the **Enable RBAC** toggle on our API. By doing this, only users with the necessary role or permission will receive this scope.

![Enable RBAC](https://sandrino.dev/assets/blog/securing-netlify-functions-with-serverless-jwt/enable-rbac.jpeg)

We can now create a role (eg: **TV Shows Viewer**), add permissions to this role (eg: the `read:shows` permissions) and then finally assign this role to a user.

![Add roles to user](https://sandrino.dev/assets/blog/securing-netlify-functions-with-serverless-jwt/user-roles.jpeg)

Users without this role will not receive the `read:shows` scope in their `access_token`. If they attempt to call the function it will result in the following error:

```js
{
  "error": "access_denied",
  "error_description": "Token does not contain the required 'read:shows' scope"
}
```

Users with the scope will be able to call the API just fine.

## ✅ Success!

And that's it. In a few simple steps we've configured Auth0, added authentication to our Gatsby application, secured our Netlify Functions and implemented RBAC. Phew! 🚀

A demo application is available in which you can test the flow end to end: [https://gatsby-auth0-netlify-functions.netlify.app/](https://gatsby-auth0-netlify-functions.netlify.app/).

The full source for the example application is available [on GitHub](https://github.com/sandrinodimattia/serverless-jwt/tree/master/examples/gatsby-auth0-netlify-functions).