API Auth for SPAs with Auth0 on Cloudflare Workers
Tags: serverless, backend, frontend, auth
Published: 2020-07-24 12:01:44

---
title: API Auth for SPAs with Auth0 on Cloudflare Workers
published: true
tags: serverless, backend, frontend, auth
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/om37cy1sh7q83lym4cqi.jpg
---

*[TL;DR Code on GitHub](https://github.com/fllstck/spa-api-auth)*

Cloudflare allows us frontend devs to build APIs in no-time. They will be edge deployed and don't need an extra API gateway! As I already wrote [in one of my previous articles](https://dev.to/fllstck/cloudflare-workers-introduction-14mo), it's built on web technology, you know from the browser: JavaScript and Service workers. 

This is a blessing and a curse. On the one hand, we can use the APIs we know from the browser; on the other hand, some Node.js packages won't work here, because the APIs are missing.

That's why I wrote this how-to. 

## Pre-Requisites

You need to sign up for [Cloudflare](https://dash.cloudflare.com/sign-up) and [Auth0](https://auth0.com/signup). They both come with a generous free plan, so no fear!

You also need a Linux or macOS system with a Node.js installation.

## Cloning the GitHub Repo

{% github fllstck/spa-api-auth %}

The repo contains a `credentials.json` where you need to add your Auth0 and Cloudflare account details and a `setup.js` that will sprinkle your credentials into your the files that need them. You can look at the `tmp.*` files to check what credentials are required for your projects later.

The Cloudflare `apiKey` can be found as Global API Key on [your Cloudflare profile](https://dash.cloudflare.com/profile/api-tokens)

The Cloudflare `accountId` can be found in the [Cloudflare dashboard](https://dash.cloudflare.com/workers/overview). You have to click on "Manage Workers" to get to it.

The Cloudflare `email` is the email address you signed up with to Cloudflare.

For the Auth0 credentials, you need to create an "Auth0 Application" first.

## Creating an Auth0 Application

Create a "SPA Application" on the [Auth0 Dashboard](https://manage.auth0.com/dashboard). There is a big orange button on the top right for that purpose.

The app name is not essential here. What is important is the "Domain" and "Client ID" that will be shown at the top of the settings after you created the app. These are the last two values needed for the `credentials.json`.

Next, update the application settings.

You have to add your Cloudflare workers.dev subdomain and URLs to the `index.html` so Auth0 knows which CORS headers it should use and where to redirect users after login.

- Application Login URI
  - `https://spa-api-auth.<CF_ACCOUNT_NAME>.workers.dev/index.html`
- Allowed Callback URLs
  - `https://spa-api-auth.<CF_ACCOUNT_NAME>.workers.dev/index.html`
- Allowed Logout URLs
  - `https://spa-api-auth.<CF_ACCOUNT_NAME>.workers.dev/index.html`
- Allowed Web Origins
  - `https://spa-api-auth.<CF_ACCOUNT_NAME>.workers.dev`
- Allowed Origins (CORS)
  - `https://spa-api-auth.<CF_ACCOUNT_NAME>.workers.dev`

## Initializing & Deploying

Now that you set up everything on "The Cloud" side of things and linked it to your codebase, you're ready to deploy!

Run the following commands:

    $ npm i
    $ npm start

This will do three things:

1. Install Cloudflare's [Wrangler CLI](https://developers.cloudflare.com/workers/tooling/wrangler) locally to the NPM project
2. run the `setup.js` that will set all the credentials
3. publish the generated `index.js` to Cloudflare Workers workers.dev domain

## View the SPA

The SPA will be served via a Cloudflare Worker and can be accessed with the following URL:

    https://spa-api-auth.<CF_ACCOUNT_NAME>.workers.dev/index.html

Just use your Cloudflare account instead of the placeholder.

## Interesting Code

The code I struggled with :D

### Cloudflare Credentials

The Wrangler CLI works with environment variables, so you can set them before running it. I did this with a shell script.

```bash
#!/bin/bash
export CF_API_KEY=%CF_API_KEY%
export CF_EMAIL=%CF_EMAIL%
```

> Note: You have to run it with `source ./setkeyenv.sh`; otherwise, the variables will be gone when the next program is run in your shell.

### Serving HTML with Cloudflare Workers

I didn't use Workers Sites here, the hosting product of Cloudflare. Instead, I integrated an HTML file as a string. This could also be done for other files like CSS, images, and JavaScript.

```javascript
if (request.url.includes("/index.html"))
  return new Response(html, {
    headers: { "Content-Type": "text/html" }
  });
```

> Note: This only works if the string isn't too big, and you have to set the Content-Type header; otherwise, the browser won't recognize the HTML.

### Getting a JWT from Auth0'S Lock Widget

The Lock widget would return something called an "opaque token" and not a JWT when I first run it. I had to supply it with the following config to get a JWT token. It will be stored inside `authResult.idToken` after an authentication happened.

```javascript
const audience = location.protocol +
  "//" + location.hostname + "/api";
const lockWidget = new Auth0Lock(
  "%AUTH0_CLIENT_ID%",
  "%AUTH0_DOMAIN%", {
    auth: {
      responseType: "id_token",
      params: { scope: "openid", audience },
    },
  })
```

### Setting the Right HTTP Header

Since I created my API, I'm free to use whatever header field I like, but I choose the `Authorization` header field because it is standard.

```javascript
fetch("/api/private", {
  headers: { Authorization: "Bearer " + idToken }
})
```

### Getting the Right Public Key from Auth0

Auth0 hosts the current keys for you, so you have to fetch them before validating them.

You get a list of keys from their `/.well-known/jwks.json` endpoint and have to find the one that corresponds to your JWT.

The key ID, or `kid`, is in the JWT header, so you have to extract it from there and use it to find the right key.

Then you have to use the WebCrypto API to convert that key to a public key.

```javascript
const [rawHeader] = request.headers
    .get("Authorization")
    .substring(6)
    .trim()
    .split(".");
const { kid } = JSON.parse(atob(rawHeader));
const request = await fetch(
  "https://%AUTH0_DOMAIN%/.well-known/jwks.json"
);
const { keys } = await request.json();
const jwk = keys.find((key) => key.kid === kid);
const publicKey = crypto.subtle.importKey(
  "jwk",
  jwk,
  { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
  false,
  ["verify"]
);
```

> Note: Auth0's `/.well-known/jwks.json` endpoint is rate-limited, and Cloudflare Workers scale pretty good, so you need to cache them in production. The best place would be Workers KV.

## Summary

Cloudflare Workers are the most light-weight FaaS system I know and are even edge deployed by default. But they are also different from Node.js, which required me to jump through some hoops to get things running.

Auth0, on the other hand, is a high level managed serverless auth service that does much of the heavy lifting for you, so while finding the public keys wasn't that straight forward, it saves so much time, you can't quite imagine.

In the end, everything worked out, and I'm happy with the result.