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, 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 and Auth0. 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
API Auth for SPAs with Auth0 on Cloudflare Workers
This example illustrates API authentication for APIs that are hosted on Cloudflare workers.
More details can be found in this article.
Prerequisites
- Cloudflare Account
- Auth0 Account
- Auth0 SPA application configured for your workers.dev subdomain
https://spa-api-auth.<YOUR_WORKERS_SUBDOMAIN>.workers.dev/index.html
Setup & Deploy
Please add your Cloudflare and Auth0 account details
to the credentials.json
before deployment.
$ npm i
$ npm start
View
The example can be viewed on your workers.dev subdomain.
https://spa-api-auth.<YOUR_WORKERS_SUBDOMAIN>.workers.dev/index.html
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
The Cloudflare accountId
can be found in the Cloudflare dashboard. 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. 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:
- Install Cloudflare's Wrangler CLI locally to the NPM project
- run the
setup.js
that will set all the credentials - 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.
#!/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.
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.
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.
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.
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.
Top comments (0)