How to add Auth0 authentication to a Webflow site

Auth0 Authentication in Webflow

What do you need to add Auth0 authentication to a Webflow site?

You need an Auth0 account configured as a Regular Web Application, a Webflow Cloud App, and three environment variables before writing any code.

Here are the prerequisites:

Once these are in place, feel free to proceed to the setup.

5 steps to add Auth0 authentication to a Webflow site with Webflow Cloud

The setup splits cleanly across three systems:

1. Configure the Auth0 Regular Web Application

In the Auth0 dashboard, open your Regular Web Application and navigate to Settings. You need to configure three URL fields and copy three values.

Under Application URIs, fill in:

Allowed Callback URLs:
http://localhost:3000/app/auth/callback, https://yoursite.webflow.io/app/auth/callback, https://yourdomain.com/app/auth/callback

Allowed Logout URLs:
http://localhost:3000, https://yoursite.webflow.io, https://yourdomain.com

Allowed Web Origins:
http://localhost:3000, https://yoursite.webflow.io, https://yourdomain.com

Register both your .webflow.io staging domain and your production custom domain in each field. Every URL must match exactly, including the protocol and path. If Auth0 receives a callback URL that isn't registered here, it rejects the request.

Click Save Changes.

Then, still on the Settings tab, copy the values for Domain, Client ID, and Client Secret. These will be added to your Cloud App environment in the next step.

2. Add Auth0 credentials to your Webflow Cloud environment

Your Cloud App needs four environment variables.

Add them to .env.local for local development:

# .env.local
AUTH0_DOMAIN=your-tenant.auth0.com
AUTH0_CLIENT_ID=your_client_id_here
AUTH0_CLIENT_SECRET=your_client_secret_here
AUTH0_CALLBACK_URL=http://localhost:3000/app/auth/callback

In your Webflow site settings, open Webflow Cloud, select your environment, and add the same four variables under Environment Variables. AUTH0_CLIENT_SECRET gives anyone who holds it the ability to exchange auth codes for tokens on your behalf. Keep it out of client-side code, git history, and any public-facing endpoint.

Checkpoint: Run next dev locally and confirm process.env.AUTH0_DOMAIN resolves.

3. Build the auth Route Handlers

You need to create three Route Handlers:

Create lib/auth.ts first with the JWT verification helper you'll use in every protected endpoint:

// lib/auth.ts
import { createRemoteJWKSet, jwtVerify, type JWTPayload } from 'jose'

const domain = process.env.AUTH0_DOMAIN!
export const JWKS = createRemoteJWKSet(
  new URL(`https://${domain}/.well-known/jwks.json`)
)

export async function verifyToken(token: string): Promise<JWTPayload> {
  const { payload } = await jwtVerify(token, JWKS, {
    issuer: `https://${domain}/`,
    audience: process.env.AUTH0_CLIENT_ID,
  })
  return payload
}

export function getTokenFromCookie(cookieHeader: string | null): string | null {
  if (!cookieHeader) return null
  const match = cookieHeader.match(/auth_token=([^;]+)/)
  return match ? decodeURIComponent(match[1]) : null
}

Now create the three Route Handlers:

// app/auth/login/route.ts
export const runtime = 'edge'

export async function GET() {
  const params = new URLSearchParams({
    response_type: 'code',
    client_id: process.env.AUTH0_CLIENT_ID!,
    redirect_uri: process.env.AUTH0_CALLBACK_URL!,
    scope: 'openid profile email',
  })

return Response.redirect(
    `https://${process.env.AUTH0_DOMAIN}/authorize?${params}`
  )
}

The login route just constructs Auth0's /authorize URL with the right query parameters and redirects the browser to it. Auth0 takes over the login UI from there. No client secret is exposed because the secret isn't used in this leg of the flow.

4. Protect your Cloud App content endpoints

Any Route Handler that returns content requiring login must verify the token before responding. This is the data-layer check that makes protection real.

Here's an example protected endpoint that returns user-specific data:

// app/api/me/route.ts
export const runtime = 'edge'

import { verifyToken, getTokenFromCookie } from '@/lib/auth'

export async function GET(request: Request) {
  const token = getTokenFromCookie(request.headers.get('cookie'))

if (!token) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 })
  }

try {
    const payload = await verifyToken(token)

return Response.json({
      name: payload.name,
      email: payload.email,
      sub: payload.sub,
    })
  } catch {
    return Response.json({ error: 'Invalid session' }, { status: 401 })
  }
}

For any Route Handler that serves gated content, follow the same pattern: call getTokenFromCookie(), call verifyToken(), return 401 if either step fails. Content is sent only if the JWT is valid.

5. Wire login and logout on the Webflow site

Add the following script to a Custom Code block on any page that has login/logout buttons. The buttons need IDs: login-btn for the login action and logout-btn for the logout action.

(function () {
  const loginBtn = document.getElementById('login-btn')
  const logoutBtn = document.getElementById('logout-btn')
  const userDisplay = document.getElementById('user-display')

// Wire login and logout buttons
  if (loginBtn) {
    loginBtn.addEventListener('click', function () {
      window.location.href = '/app/auth/login'
    })
  }

if (logoutBtn) {
    logoutBtn.addEventListener('click', function () {
      window.location.href = '/app/auth/logout'
    })
  }

// Fetch user info from the authenticated Cloud App endpoint
  async function loadUserInfo() {
    try {
      const res = await fetch('/app/api/me', { credentials: 'include' })

if (!res.ok) {
        // Not authenticated: show login state
        if (loginBtn) loginBtn.style.display = 'block'
        if (logoutBtn) logoutBtn.style.display = 'none'
        return
      }

const user = await res.json()

// Authenticated: update UI
      if (loginBtn) loginBtn.style.display = 'none'
      if (logoutBtn) logoutBtn.style.display = 'block'
      if (userDisplay) userDisplay.textContent = `Hi, ${user.name}`
    } catch {
      // Network error: show login state as fallback
      if (loginBtn) loginBtn.style.display = 'block'
      if (logoutBtn) logoutBtn.style.display = 'none'
    }
  }

loadUserInfo()
})()

What causes Auth0 authentication to fail on a Webflow Cloud App?

Most failures stem from one of three causes:

Auth0 rejects the callback with "callback URL mismatch"

This means the URL your app is redirecting to after login isn't registered in the Allowed Callback URLs field.

Checkpoint: Validate your URLs and settings.

Token verification throws "jwt audience invalid"

This means the audience parameter in jwtVerify() doesn't match the aud claim in the token.

Checkpoint: Verify your Auth0 Client ID.