Cloudflare Docs
Workers
Visit Workers on GitHub
Set theme to dark (⇧+D)

Authorize users with Auth0

Before you start

All of the tutorials assume you have already completed the Get started guide , which gets you set up with a Cloudflare Workers account, and the Workers CLI tool, Wrangler.

Overview

In this tutorial, you will integrate Auth0, an identity management platform, into a Cloudflare Workers application. Adding authorization and authentication to an application is a common task for developers. By implementing it using Cloudflare Workers, you can take advantage of the Workers platform to simplify how and when your application needs user data.

What you will learn

  • How to authorize and authenticate users in Workers.
  • How to persist authorization credentials inside of Workers KV.
  • How to use Auth0 user information inside of your Workers application.

Set up Auth0

If you do not already have an Auth0 account, sign up for a free account at auth0.com. This tutorial supports integration with Auth0’s free tier.

Configure an Auth0 application

Every Auth0 account contains applications, which allow developers to create login/signup flows that are verified by Auth0. To integrate Auth0 with Workers, create an application in your Auth0 dashboard. If you have created an account for this tutorial, the Default (Generic) application provided by Auth0 will work; otherwise, create a new application with the type Regular Web Application.

Creating an Auth0 application

Inside of your application’s settings, the client ID and client secret are keys that you will provide to your Workers application to authenticate with Auth0. There are several settings and configuration options, but relevant to this tutorial are the Allowed Callback URLs and Allowed Web Origins options. In the Publish section of this tutorial, you will later fill in these values with the final deployed URL of your application.

Generate a new project

Using wrangler’s generate command, begin building a new application using a Workers template. For this tutorial, you will modify the default template for Workers Sites , which deploys a static HTML application:

---
header: Generate a new project
---
$ wrangler generate --site my-auth-example

Building an authorizer

Before implementing an authorizer in your application, which will verify that a user is logged in, it is useful to understand how Auth0’s login flow works. The condensed version of this flow is below (review a longer writeup in Auth0’s documentation):

  1. A user makes a request to the Workers application.
  2. If the user is not logged in, they are redirected to the login page.
  3. After logging in, the user is redirected back to the Workers application with a login code query parameter.
  4. The Workers application takes the login code parameter and exchanges it with Auth0 for authorization tokens.

In a traditional application that is attached to a database, the authorization tokens Auth0 returns are often persisted in a database. This will allow users to return to the application and continue to use it without the need for re-authorization. With a Workers application, you have access to a quick and easy-to-use data storage solution that lives right next to your serverless application: Workers KV. Using Workers KV, you will store authorization tokens and tie them to a user using an authorization cookie.

Auth0 Flow

Auth0 Flow Diagram courtesy of Auth0

Authenticating a user

Begin implementing the login flow described in the previous section. When a user makes a request to the Workers application, you should verify that the user is authenticated. To define this logic, create a new file — workers-site/auth0.js- which will contain the authorization logic for your application:

---
filename: workers-site/auth0.js
---
const auth0 = {
  domain: AUTH0_DOMAIN,
  clientId: AUTH0_CLIENT_ID,
  clientSecret: AUTH0_CLIENT_SECRET,
  callbackUrl: AUTH0_CALLBACK_URL,
}

const redirectUrl = state => `${auth0.domain}/authorize?response_type=code&client_id=${auth0.clientId}&redirect_uri=${auth0.callbackUrl}&scope=openid%20profile%20email&state=${encodeURIComponent(state)}`

const generateStateParam = () => "stub"

const verify = async event => {
  // Verify a user based on an auth cookie and Workers KV data
  return { accessToken: "123" }
}

// Returns an array with the format
//   [authorized, context]
export const authorize = async event => {
  const authorization = await verify(event)
  if (authorization.accessToken) {
    return [true, { authorization }]
  } else {
    const state = await generateStateParam()
    return [false, { redirectUrl: redirectUrl(state) }]
  }
}

The auth0 object wraps several secrets, which are encrypted values that can be defined and used by your script. In the Publish section of this tutorial, you will define these secrets using the wrangler secret command.

The generateStateParam function will be used to prevent Cross-Site Request Forgery (CSRF) attacks. For now, you will return a string stub but later in the tutorial, generateStateParam will generate a random state parameter that you will store in Workers KV to verify incoming authorization requests.

The verify function, which you will stub out in your first pass through this file, will check for an authorization key and look up a corresponding value in Workers KV. For now, you will simply return an object with an accessToken string to simulate an authorized request.

The authorize function, which should be exported from ./auth0.js, will wait for the response from the verify function, and return an array that can be used to determine how the application should proceed.

In workers-site/index.js, import the authorize function from ./auth0.js and use it inside of your handleEvent function. Note that by default the Workers Sites template contains code for rendering your Workers Site from Workers KV. To keep that code functioning, make sure that you replace handleEvent as defined below:

---
filename: workers-site/index.js
highlight: [1, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]
---
import { authorize } from "./auth0"

addEventListener("fetch", event => event.respondWith(handleEvent(event)))

async function handleEvent(event) {
  let request = event.request
  let response = new Response(null)
  const url = new URL(request.url)

  try {
    const [authorized, { authorization, redirectUrl }] = await authorize(event)

    // BEGINNING OF WORKERS SITES
    // Make sure to not touch this code for the majority of the tutorial.
    response = getAssetFromKV(event)
    // END OF WORKERS SITES

    return response
  } catch (e) {
    return new Response(e.message || e.toString(), { status: 500 })
  }
}

The authorize function returns an array with a boolean authorized and a context object which will contain either an authorization object or a redirectUrl.

Building on the past code sample, you can check that a user is authorized and that the authorization object contains an accessToken. Then update the incoming request to contain an Authorization header:

---
filename: workers-site/index.js
highlight: [3, 4, 5, 6, 7, 8, 9, 10, 11]
---
async function handleEvent(event) {
  try {
    const [authorized, { authorization, redirectUrl }] = await authorize(event)
    if (authorized && authorization.accessToken) {
      request = new Request(request, {
        headers: {
          Authorization: `Bearer ${authorization.accessToken}`,
        },
      })
    }
    // END OF AUTHORIZATION CODE BLOCK

    // BEGINNING OF WORKERS SITES
  }
}

If a user is not authorized, redirect them to Auth0’s login page by calling Response.redirect and passing in the redirectUrl:

---
filename: workers-site/index.js
highlight: [6, 7, 8, 9, 10]
---
async function handleEvent(event) {
  try {
    const [authorized, { authorization, redirectUrl }] = await authorize(event)
    // END OF AUTHORIZATION CODE BLOCK

    // BEGINNING OF REDIRECT CODE BLOCK
    if (!authorized) {
      return Response.redirect(redirectUrl)
    }
    // END OF REDIRECT CODE BLOCK

    // BEGINNING OF WORKERS SITES
  }
}

When a user logs in via Auth0’s login form, they will be redirected back to the callback URL specified by your application. In the next section , you will handle that redirect and get a user access token as part of the login code flow.

Auth0 Login Form

Handling a login redirect

To handle the login code flow as defined by Auth0, you will handle an incoming request to the /auth path, which will contain a code parameter. By making another API request to Auth0, providing your applications’s client ID and secret, you can exchange the login code for an access token.

To begin, add a new block of code to handleEvent, which will parse the request URL, and if the URL path matches /auth, call the newly imported handleRedirect function from ./auth0.

---
filename: workers-site/index.js
highlight: [1, 5, 6, 7, 8, 9]
---
import { authorize, handleRedirect } from "./auth0"

async function handleEvent(event) {
  try {
    // BEGINNING OF HANDLE AUTH REDIRECT CODE BLOCK
    if (url.pathname === "/auth") {
      const authorizedResponse = await handleRedirect(event)
    }
    // END OF HANDLE AUTH REDIRECT CODE BLOCK

    // BEGINNING OF REDIRECT CODE BLOCK
  }
}

The handleRedirect function, which you will export from workers-site/auth0.js, will parse the incoming URL, and pass the code login parameter to exchangeCode. Check for a state parameter, which you will use to prevent CSRF attacks. This state parameter should be matched to a known key in KV, indicating that the authorization request is valid:

---
filename: workers-site/auth0.js
---
export const handleRedirect = async event => {
  const url = new URL(event.request.url)

  const state = url.searchParams.get("state")
  if (!state) {
    return null
  }

  const storedState = await AUTH_STORE.get(`state-${state}`)
  if (!storedState) {
    return null
  }

  const code = url.searchParams.get("code")
  if (code) {
    return exchangeCode(code)
  }
  return {}
}

Define exchangeCode, which will take the code parameter, and make a request back to Auth0, exchanging it for an access token:

---
filename: workers-site/auth0.js
---
const exchangeCode = async code => {
  const body = JSON.stringify({
    grant_type: "authorization_code",
    client_id: auth0.clientId,
    client_secret: auth0.clientSecret,
    code,
    redirect_uri: auth0.callbackUrl,
  })

  // We’ll define persistAuth in the next section
  return persistAuth(
    await fetch(AUTH0_DOMAIN + "/oauth/token", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body,
    })
  )
}

Persisting authorization data in Workers KV

Define the persistAuth function to handle the request and parse the appropriate authorization information from it. Begin by parsing the JSON body returned back from Auth0:

---
filename: workers-site/auth0.js
---
const persistAuth = async exchange => {
  const body = await exchange.json()

  if (body.error) {
    throw new Error(body.error)
  }

  console.log(body) // { access_token: "...", id_token: "...", ... }
}

The body object — assuming no errors — will contain an access_token, id_token, and other fields that you should persist inside of Workers KV, a key-value store that you can access inside of your Workers scripts. When you store data inside Workers KV, you need to persist it using a key. The id_token field, which is returned by Auth0, is a JSON Web Token (JWT) that contains a sub field, a unique identifier for each user. Decode the JSON Web Token and parse it into an object:

---
filename: workers-site/auth0.js
highlight: [37]
---
// https://github.com/pose/webcrypto-jwt/blob/master/workers-site/index.js
const decodeJWT = function(token) {
  var output = token
    .split(".")[1]
    .replace(/-/g, "+")
    .replace(/_/g, "/")
  switch (output.length % 4) {
    case 0:
      break
    case 2:
      output += "=="
      break
    case 3:
      output += "="
      break
    default:
      throw "Illegal base64url string!"
  }

  const result = atob(output)

  try {
    return decodeURIComponent(escape(result))
  } catch (err) {
    console.log(err)
    return result
  }
}

const persistAuth = async exchange => {
  const body = await exchange.json()

  if (body.error) {
    throw new Error(body.error)
  }

  const decoded = JSON.parse(decodeJWT(body.id_token))
}

To ensure that the ID token you have received is valid, you should do a number of checks on the decoded token object, as per the OpenID Connect Core 1.0 spec. Update the persistAuth function to validate the token — if it is not valid, return an object indicating that the response is invalid:

---
filename: workers-site/auth0.js
highlight: [1, 2, 3, 9, 10, 11, 12]
---
const validateToken = token => {
  // Stubbed function
}

const persistAuth = async exchange => {
  // Previous code

  const decoded = JSON.parse(decodeJWT(body.id_token))
  const validToken = validateToken(decoded)
  if (!validToken) {
    return { status: 401 }
  }
}

Inside of validateToken examine fields inside of the decoded token, ensuring that:

  • The iss field matches the AUTH0_DOMAIN secret
  • The aud field matches the AUTH0_CLIENT_ID secret
  • The exp field is after the current time
  • The iat field was issued in the last day

The code for this will use a try/catch block, throwing an error and returning false if any of the above criteria are not true:

---
filename: workers-site/auth0.js
---
const validateToken = token => {
  try {
    const dateInSecs = d => Math.ceil(Number(d) / 1000)
    const date = new Date()

    let iss = token.iss

    // ISS can include a trailing slash but should otherwise be identical to
    // the AUTH0_DOMAIN, so we should remove the trailing slash if it exists
    iss = iss.endsWith("/") ? iss.slice(0, -1) : iss

    if (iss !== AUTH0_DOMAIN) {
      throw new Error(
        `Token iss value (${iss}) doesn’t match AUTH0_DOMAIN (${AUTH0_DOMAIN})`,
      )
    }

    if (token.aud !== AUTH0_CLIENT_ID) {
      throw new Error(
        `Token aud value (${token.aud}) doesn’t match AUTH0_CLIENT_ID (${AUTH0_CLIENT_ID})`,
      )
    }

    if (token.exp < dateInSecs(date)) {
      throw new Error(`Token exp value is before current time`)
    }

    // Token should have been issued within the last day
    date.setDate(date.getDate() - 1)
    if (token.iat < dateInSecs(date)) {
      throw new Error(`Token was issued before one day ago and is now invalid`)
    }

    return true
  } catch (err) {
    console.log(err.message)
    return false
  }
}

With the decoded JWT available and validated, you can hash and salt the sub value and use it as a unique identifier for the current user. To do this, use the Web Crypto API available inside the Workers runtime. Combine the SALT value, a secret that you will set later in the tutorial, with the sub value. After creating a SHA-256 digest of these combined strings, use the digest as the key for storing the user’s JWT in Workers KV:

---
filename: workers-site/auth0.js
highlight: [4, 5, 6, 7, 8, 9]
---
const persistAuth = async exchange => {
  // ...

  const text = new TextEncoder().encode(`${SALT}-${decoded.sub}`)
  const digest = await crypto.subtle.digest({ name: "SHA-256" }, text)
  const digestArray = new Uint8Array(digest)
  const id = btoa(String.fromCharCode.apply(null, digestArray))

  await AUTH_STORE.put(id, JSON.stringify(body))
}

Once the user’s authentication data has been stored in KV, you need to associate the user with that data. To do this, set a cookie, setting the value to the encrypted id string you just defined.

This cookie will be used as you fill out the verify function defined earlier in the tutorial . Set the cookie to expire in a day (though this is easily customizable to your application’s needs). To persist this cookie, return an object containing some data for a Response instance, redirecting the user via an HTTP 302 response to the / path, with a Set-cookie header:

---
filename: workers-site/auth0.js
highlight: [1, 6, 7, 8, 9, 10, 11, 12, 13, 14]
---
const cookieKey = "AUTH0-AUTH"

const persistAuth = async exchange => {
  // previous code

  const date = new Date()
  date.setDate(date.getDate() + 1)

  const headers = {
    Location: "/",
    "Set-cookie": `${cookieKey}=${id}; Secure; HttpOnly; SameSite=Lax; Expires=${date.toUTCString()}`,
  }

  return { headers, status: 302 }
}

With your authorization logic defined, finish the corresponding code in workers-site/index.js. Knowing that the handleRedirect function will pass back an object with Response options, you can make a new Response object, passing in the information from the existing response, and adding the new headers and status code from handleRedirect. This will redirect the user to the applications’s root path, setting a cookie to indicate that they are authorized for future requests:

---
filename: workers-site/index.js
highlight: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
---
async function handleEvent(event) {
  try {
    // BEGINNING OF HANDLE AUTH REDIRECT CODE BLOCK
    if (url.pathname === "/auth") {
      const authorizedResponse = await handleRedirect(event)
      if (!authorizedResponse) {
        return new Response("Unauthorized", { status: 401 })
      }
      response = new Response(response.body, {
        response,
        ...authorizedResponse,
      })
      return response
    }
    // END OF HANDLE AUTH REDIRECT CODE BLOCK

    // BEGINNING OF WORKERS SITES
  }
}

Implementing Cross-Site Request Forgery (CSRF) protection

To correctly protect against CSRF attacks, your application needs to provide a state parameter to the Auth0 login URL. When the user logs in and is redirected back to your application, you can compare the state parameter in the redirect URL to your previous piece of state, confirming that the user is beginning and ending the login flow via your application.

Generate this piece of state using csprng.xyz, a Cloudflare API service for generating random data. The API endpoint csprng.xyz/v1/api returns a JSON object with the key Data that you will use as the random value:

{
  "Data": "PTBsWkQ7Zg5pAXAq5/YJS1mtFL97q1k/qUVJNdirEl0=",
  "Time": "2020-05-29T13:22:54.840Z",
  "Status": 200
}

The application will use Workers KV to persist this random data for one day (86,400 seconds), which is configured via the expirationTtl option. After one day, KV will automatically discard the state-${state} key. Replace the stubbed generateStateParam function in workers-site/auth0.js

---
filename: workers-site/auth0.js
---
const generateStateParam = async () => {
  const resp = await fetch("https://csprng.xyz/v1/api")
  const { Data: state } = await resp.json()
  await AUTH_STORE.put(`state-${state}`, true, { expirationTtl: 86400 })
  return state
}

Verifying the token and retrieving user info

With your application persisting authentication data in Workers KV and associating it to the current user via a cookie, you are now prepared to fill out the verify function defined earlier in the tutorial . This function will look at the Cookie header and try to locate the authentication information that you persisted in Workers KV.

To begin, install the NPM package cookie, which you will use to simplify parsing the Cookie header in the request:

---
header: Install cookie package
---
$ cd workers-site && npm install cookie

In workers-site/auth0.js, you can begin to write the contents of the verify function. Start by parsing the Cookie header, looking for your cookieKey as defined earlier in the tutorial:

---
filename: workers-site/auth0.js
highlight: [1, 4, 5, 6, 7, 8, 9, 10]
---
import cookie from "cookie"

const verify = async event => {
  const cookieHeader = event.request.headers.get("Cookie")
  if (cookieHeader && cookieHeader.includes(cookieKey)) {
    const cookies = cookie.parse(cookieHeader)
    if (!cookies[cookieKey]) return {}
    const sub = cookies[cookieKey]
  }
  return {}
}

With the unique ID sub parsed from the Cookie header, use it to retrieve the user information you previously stored in KV. First, do a lookup to Workers KV using the sub field as a key — if it is not found, throw an Error. Next, take that data from Workers KV and attempt to parse it as JSON — if that fails, another Error will be thrown:

---
filename: workers-site/auth0.js
highlight: [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
---
const verify = async event => {
  const cookieHeader = event.request.headers.get("Cookie")
  if (cookieHeader && cookieHeader.includes(cookieKey)) {
    const cookies = cookie.parse(cookieHeader)
    if (!cookies[cookieKey]) return {}
    const sub = cookies[cookieKey]

    const kvData = await AUTH_STORE.get(sub)
    if (!kvData) {
      throw new Error("Unable to find authorization data")
    }

    let kvStored
    try {
      kvStored = JSON.parse(kvData)
    } catch (err) {
      throw new Error("Unable to parse auth information from Workers KV")
    }

    const { access_token: accessToken, id_token: idToken } = kvStored
  }
}

Finally, decode the idToken stored in KV. This includes the profile and email scopes you requested from Auth0 when the user logged in, which you will return as userInfo, along with accessToken and idToken:

---
filename: workers-site/auth0.js
highlight: [20, 21, 22, 24]
---
const verify = async event => {
  const cookieHeader = event.request.headers.get("Cookie")
  if (cookieHeader && cookieHeader.includes(cookieKey)) {
    const cookies = cookie.parse(cookieHeader)
    if (!cookies[cookieKey]) return {}
    const sub = cookies[cookieKey]

    const kvData = await AUTH_STORE.get(sub)
    if (!kvData) {
      throw new Error("Unable to find authorization data")
    }

    let kvStored
    try {
      kvStored = JSON.parse(kvData)
    } catch (err) {
      throw new Error("Unable to parse auth information from Workers KV")
    }

    const { access_token: accessToken, id_token: idToken } = kvStored
    const userInfo = JSON.parse(decodeJWT(idToken))
    return { accessToken, idToken, userInfo }
  }
  return {}
}

As a summary, this verify function will now correctly verify your application’s users based on the Cookie field and make any authorization information available as part of the authorization object:

---
filename: workers-site/auth0.js
---
export const authorize = async event => {
  const authorization = await verify(event)
  if (authorization.accessToken) {
    return [true, { authorization }]
  } else {
    const state = await generateStateParam()
    return [false, { redirectUrl: redirectUrl(state) }]
  }
}

By implementing this function, you have now completed the authorization/authentication portion of the tutorial. Your application will authorize any incoming users, redirecting them to Auth0 and verifying their access tokens before they are allowed to see your Workers Site content.

To configure your deployment and publish the application, you can go to the Publish section , but in the next few portions of the tutorial you will focus on some of the more interesting aspects of this project; for example, accessing user information within your application, edge state hydration, logging out users, and making the application more production-ready with some improvements and customizations.

Improvements and customizations

This tutorial introduces concepts for implementing authentication in Workers using Auth0. There are several potential customizations and improvements to this codebase that are out-of-scope for this tutorial. This tutorial will briefly mention a few in this section, along with links to learn more.

Using user data in your application

In the previous section of the tutorial, you made a request to Auth0’s /userinfo endpoint, which provides information such as name and email address for use in your application. Using Workers’ HTML Rewriter , you can embed the userInfo object that you received from Auth0 directly into your site by creating an instance of the HTMLRewriter class and attaching a hydrateState handler to any found head tags that pass through the rewriter. The hydrateState handler will add a new script tag with an ID of edge_state, which you can parse and utilize in any front-end JavaScript code you will deploy with your application. Instead of simply returning response in handleEvent, replace it with the HTML rewriter code and return a transformed version of response:

---
filename: workers-site/index.js
highlight: [1, 2, 3, 4, 5, 6, 7, 19, 20, 21, 22, 23]
---
const hydrateState = (state = {}) => ({
  element: head => {
    const jsonState = JSON.stringify(state)
    const scriptTag = `<script id="edge_state" type="application/json">${jsonState}</script>`
    head.append(scriptTag, { html: true })
  },
})

async function handleEvent(event) {
  try {
    // BEGINNING OF WORKERS SITES
    // Note the addition of the `await` keyword
    response = await getAssetFromKV(event)
    // END OF WORKERS SITES

    // Remove the line of code below
    // return response

    // BEGINNING OF STATE HYDRATION CODE BLOCK
    return new HTMLRewriter()
      .on("head", hydrateState(authorization.userInfo))
      .transform(response)
    // END OF STATE HYDRATION CODE BLOCK
  }
  // ...
}

For a more detailed example of this functionality, refer to the source code for this tutorial, which shows how to integrate this information using the JavaScript framework Alpine.js. In this example, the userInfo object is embedded into the script#edge_state tag, and when the site is rendered in the client’s browser, the user’s name (or email address, if the user’s name is not provided) is displayed:

User info

Logging out users

While a user’s authentication cookie expires after a day, you may want to offer the ability for a user to log out manually. To implement this feature, instead of letting the cookie expire automatically, your Workers application should pass a Set-cookie header that nulls out the cookieKey you previously defined. Create a logout function in workers-site/auth0.js and import it in workers-site/index.js, calling it when a user requests /logout:

---
filename: workers-site/auth0.js
---
export const logout = event => {
  const cookieHeader = event.request.headers.get("Cookie")
  if (cookieHeader && cookieHeader.includes(cookieKey)) {
    return {
      headers: {
        "Set-cookie": `${cookieKey}=""; SameSite=Lax; Secure;`,
      },
    }
  }
  return {}
}
---
filename: workers-site/index.js
highlight: [1, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
---
import { ..., logout } from "./auth0.js"

async function handleEvent(event) {
  try {
    // END OF WORKERS SITES CODE BLOCK

    // BEGINNING OF LOGOUT CODE BLOCK
    if (url.pathname === "/logout") {
      const { headers } = logout(event)
      return headers
        ? new Response(response.body, {
            ...response,
            headers: Object.assign({}, response.headers, headers)
          })
        : Response.redirect(url.origin)
    }
    // END OF LOGOUT CODE BLOCK

    // BEGINNING OF STATE HYDRATION CODE BLOCK
  }
}

In your Workers Site, you can add a Log out link, which will send users to the /logout route, and clear their auth cookie via the Set-cookie header:

<a href="/logout">Log out</a>

An example logout HTML page could look like this:

---
filename: public/logout/index.html
---
<h1>You’re logged out</h1>
<div><a href="/">Log back in</a></div>

When the user refreshes the page, they will be identified as an unauthorized user, and be redirected to Auth0’s login page. For a more advanced implementation of logout functionality, you may choose to always return a redirect to your application’s root path. This redirect will force every user to sign in again immediately after logging out:

---
filename: workers-site/auth0.js
---
export const logout = event => {
  const cookieHeader = event.request.headers.get("Cookie")
  if (cookieHeader && cookieHeader.includes(cookieKey)) {
    return {
      headers: {
        "Location": "/",
        "Set-cookie": `${cookieKey}="";`
      },
      status: 302
    }
  }
  return {}
}

Deploying to origin/originless

While this tutorial assumes that you are deploying a Workers Sites application, you may want to put this authorization logic in front of an existing domain. This concept, known as deploying to an origin, is in contrast to the originless deploy, where your Workers deployment is the final destination for any requests from users of your application.

The next section of this tutorial, Publish , assumes deployment to Workers’ built-in deployment target, *.workers.dev, but if you want to handle deploying to an existing domain, known commonly as a zone, you will need to take the following steps:

  1. Update the handleEvent function to make a request to your origin.

While you are currently using Workers Sites, update workers-site/index.js and replace the Workers Site code with a request to your origin:

---
filename: workers-site/index.js
highlight: [10]
---
async function handleEvent(event) {
  try {
    // BEGINNING OF WORKERS SITES
    // ↳ this can now be thought of as "BEGINNING OF ORIGIN REQUEST"

    // Replace the below line of code
    // response = await getAssetFromKV(event)

    // With a fetch request to your origin
    response = await fetch(request)
    // END OF WORKERS SITES
    // ↳ this can now be thought of as "END OF ORIGIN REQUEST"
  }
}

Given an example configuration and deployment of https://my-auth.signalnerve.com, your Workers script will continue to authorize users, but when it comes time to return a response back to the user, the code will now make a request to my-auth.signalnerve.com, and, if you have set up the edge state hydration concept explored earlier in this tutorial , you will still receive the same user information in your HTML response as originally configured.

  1. Configure your wrangler.toml file to associate your Workers script with a zone.

Associating a configured zone from your Cloudflare account is covered in the section Configure for deploying to a registered domain section of Get started . In the Publish section of this guide, you will learn how to configure the file wrangler.toml to deploy to *.workers.dev — make sure you review the Get started guide linked above so you can understand how these approaches differ.

You will need to ensure that the zone_id and route keys are defined in your wrangler.toml and that the workers_dev key is disabled. You may also choose to entirely remove the [site] block from your wrangler.toml file, which will stop wrangler from uploading the contents of your project’s public folder to Workers KV:

---
filename: wrangler.toml
highlight: [2, 3]
---
workers_dev = false
zone_id = "123abc"
route = "https://my-auth.signalnerve.com/*"

## The below information can be removed
[site]
bucket = "./public"

Take note of the route field in your wrangler.toml file, as this determines when your Workers script will run on your zone. For compatibility with the code you have written so far in this tutorial, the wildcard route /* should be used, which will match every request to your site and pass it through your Workers script.

Deploying an origin version of this code can be a useful approach for users who want to utilize this functionality in an existing codebase or site.

Using the open-source version of this package

The open-source example repository for this tutorial showcases all the functionality outlined in this tutorial — such as originless/origin deploys and edge state hydration. If you want to get started with this project, refer to the linked GitHub repository.

Publish

You are ready to deploy your application to Workers. Before you can deploy your application, you need to set some configuration values both in Workers and Auth0.

Configuring wrangler.toml

The wrangler.toml generated as part of your application tells wrangler how and where to deploy your application. Using the Configuring your project section of the Get started as a guide, populate wrangler.toml with your account ID, which will allow you to deploy your application to your Cloudflare account:

---
filename: wrangler.toml
---
account_id = "$accountId"

Creating a Workers KV namespace

In the code for this tutorial, you have used the constant AUTH_STORE to refer to a Workers KV namespace where you store authorization information for your users. Before you can deploy this project, create a Workers KV namespace and attach it to your Workers application. Using wrangler, create this Workers KV namespace directly from the command line:

---
header: Create a new KV namespace with Wrangler
---
$ wrangler kv:namespace create AUTH_STORE

The output of running that command will be a block of code that you can paste directly into wrangler.toml, which will bind your new Workers KV namespace to your application. It should look something like this:

---
filename: wrangler.toml
highlight: [3, 4, 5]
---
## ...existing account and zone information

kv_namespaces = [
  { binding = "AUTH_STORE", id = "$YOURNAMESPACEID" }
]

## ...existing workers site configuration

Secrets

In workers-site/auth0.js, this tutorial referred to several Auth0 constants, such as client ID, and secret. Before you can deploy your application, set up these secrets, using wrangler’s secret command, which will make them available to reference as constants in the codebase.

Below is the complete list of secrets that the Workers script will look for when it processes a client request:

wrangler secret key Value
AUTH0_DOMAIN Your Auth0 domain (for example, https://myapp.auth0.com). It must include the scheme https:// and should be a valid URL
AUTH0_CLIENT_ID Your Auth0 client ID
AUTH0_CLIENT_SECRET Your Auth0 client secret
AUTH0_CALLBACK_URL The callback url for your application (refer to Setting the callback URL below)
SALT A secret string used to encrypt user sub values (refer to Setting the salt below)

For each key, find the corresponding value in your Auth0 application settings page.

---
filename: workers-site/auth0.js
---
let AUTH0_DOMAIN, AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET, AUTH0_CALLBACK_URL, SALT

With these constants stubbed, you can publish your application:

---
header: Publish your project
---
$ wrangler publish

After your application has successfully published, remove the constants line from auth0.js:

---
filename: workers-site/auth0.js
---
// Delete the below line.
// let AUTH0_DOMAIN, AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET, AUTH0_CALLBACK_URL, SALT

Using wrangler secret, set each secret directly in the command line:

---
header: Set secret values
---
$ wrangler secret put AUTH0_DOMAIN
$ wrangler secret put AUTH0_CLIENT_ID
$ wrangler secret put AUTH0_CLIENT_SECRET
$ wrangler secret put AUTH0_CALLBACK_URL
$ wrangler secret put SALT

Auth0 Settings

Setting the callback url

To correctly set the callback URL for your application, you will need to determine where your application will be deployed. Regardless of whether you are setting up an originless or origin-based deploy, the callback handler for this project is defined at /auth. This means that if you are testing or deploying a staging version of this project, your callback URL will likely be something like https://my-auth-example.signalnerve.workers.dev/auth, or for production, you should set it to something like https://my-production-app.com/auth.

This tutorial assumes the usage of a *.workers.dev subdomain, which is provided for free to all developers using Workers. You can determine your callback URL by combining the name of your application (chosen during the wrangler generate phase – in this tutorial, my-auth-example was used) and your *.workers.dev subdomain, as seen below:

https://$applicationName.$subdomain.workers.dev/auth

Following this example, the callback URL for my application is https://my-auth-test.signalnerve.workers.dev/auth.

Setting the salt

In order to safely store user IDs (the sub value from Auth0) in the cookie we set in the browser, you should always refer to them by a value that cannot be easily guessed by someone else. To do this, generate a unique value based on the user’s ID and a salt: a secret value provided by the application.

To generate a salt: make a new, random string, and save it as a secret for your application. Previously, this tutorial used csprng.xyz API to generate a random piece of state to protect against CSRF attacks. Open https://csprng.xyz/v1/api in your browser, and copy the Data field to your clipboard. If you would like to generate a string yourself, remember that it is important that the salt cannot easily be guessed.

With a random string generated, set it using wrangler secret:

---
header: Set the SALT secret
---
$ wrangler secret put SALT

Allowed origin/callback URLs

Note that Auth0 has security defaults and any callback URLs or origins that you will use as sources to log in from need to be explicitly provided in the Auth0 dashboard as part of your application configuration. Using the above *.workers.dev example, ensure the following values are set in the application settings page of your Auth0 dashboard, along with any additional URLs used as part of testing (for example, localhost:8787 for [wrangler dev][/cli-wrangler/commands#dev] usage):

URL Description
https://$applicationName.$subdomain.workers.dev/auth Allowed Callback URL
https://$applicationName.$subdomain.workers.dev Allowed Origin

Auth0 allowed URLs

Publishing your application

With your wrangler.toml file successfully configured, use wrangler’s publish command to deploy your application:

---
header: Publish your project
---
$ wrangler publish

Wrangler will compile your code, upload the associated Workers Sites folder (public, by default), and begin handling requests sent to your *.workers.dev application, or to your zone. To confirm everything works as expected, you should:

  1. Visit your application (for example, my-auth-test.signalnerve.workers.dev), which should redirect you to Auth0’s login page.
  2. Log in with an email/password or the social identity provider of your choice, if enabled.
  3. Let Auth0 redirect you to /auth, and then to /. As this is happening, your Workers application has exchanged a login code with Auth0 for an access token, persisted it to Workers KV, and registered you as an authorized user via a cookie.
  4. If you see your site, you have successfully authorized users to your Workers application, using Auth0.

Example GIF

Conclusion

By completing this tutorial, you have successfully built an application that authorizes and authenticates users on the edge using Cloudflare Workers. To see the final version of the project you built in this tutorial, refer to the example GitHub repository: signalnerve/workers-auth0-example.

You can build a lot more with Workers, such as serving static and JAMstack-style applications using Workers Sites, or transforming HTML responses using HTMLRewriter. Below are some more tutorials for you to review and experiment with.