announcements

Adding Auth0 to Hono on Cloudflare Workers: A Practical Guide

Add Auth0 login, logout, sessions, and token refresh to a Hono app on Cloudflare Workers with one middleware call. A practical guide to the new @auth0/auth0-hono SDK (beta).

If you have shipped anything on Cloudflare Workers lately, you have probably reached for Hono. It is small, fast, and runs the same code on Workers, Node, Bun, and Deno. The one thing it did not have was a first-party way to handle Auth0 login. So you wrote your own middleware, copied an OpenID Connect flow from a gist, and hoped you got the PKCE and state checks right.

Today that changes. @auth0/auth0-hono is now in beta. One middleware call wires up login, logout, sessions, and token refresh as native Hono middleware. It runs on the edge out of the box, with nothing extra to configure.

Here is how to add it, what it does for you, and what "beta" means before you put it in production.

TL;DR:

  • npm install @auth0/auth0-hono, then app.use('*', auth0()). That's login, logout, callback, and session management.
  • Protect routes with requiresAuth(). Read the user from c.var.auth0.user.
  • Runs on Cloudflare Workers, Node, Bun, Deno, and Vercel Edge from one codebase, with no platform-specific workarounds.
  • Sessions are encrypted cookies by default, with a pluggable store for larger payloads.
  • It is beta: the feature set is production-grade for Node and Workers, but the API can still shift before general availability. Feedback is appreciated!

Hono has crossed thirty thousand GitHub stars and pulls more than forty million npm installs a week. It has become the default web framework for people building on Cloudflare Workers, and it is TypeScript-first in a way that makes the rest of your stack easier.

Now there is an official Auth0 SDK for Hono. It handles the full OpenID Connect flow for you: token exchange, state validation, session encryption and runs natively across Node.js, Cloudflare Workers, and other edge runtimes.

Securing Your App with @auth0/auth0-hono

Let's start with. Install the package:

npm install @auth0/auth0-hono

Then add one middleware:

import { Hono } from 'hono'  
import { auth0, requiresAuth } from '@auth0/auth0-hono'

const app = new Hono()

// Add auth to every route  
app.use('*', auth0())

// Public route  
app.get('/', (c) => c.text('Home'))

// Protected route  
app.get('/profile', requiresAuth(), (c) => {  
  const user = c.var.auth0.user  
  return c.json({ name: user?.name, sub: user?.sub })  
})

export default app

That single auth0() call mounts the login, callback, and logout routes (/auth/login, /auth/callback, /auth/logout), handles backchannel logout, encrypts the session cookie, and loads the user onto c.var.auth0 for every request. Backchannel logout lets Auth0 notify your app to end a session server-to-server, so a logout in one place signs the user out everywhere. You did not write an OpenID Connect (OIDC) handler. You just added one line.

Configuration comes from environment variables, which work the same way on every runtime:

AUTH0_DOMAIN=tenant.auth0.com  
AUTH0_CLIENT_ID=abc123  
AUTH0_CLIENT_SECRET=secret123  
AUTH0_SESSION_ENCRYPTION_KEY=very_long_string_with_at_least_32_characters  
APP_BASE_URL=https://myapp.com

You will find these values in your Auth0 Dashboard under your application's settings.

Protecting Routes with requiresAuth()

requiresAuth() is a normal Hono middleware, so it composes the way you would expect. Drop it in front of any route or route group:

app.get('/dashboard', requiresAuth(), (c) => {  
  // c.var.auth0.user is guaranteed to exist here  
  return c.json(c.var.auth0.user)  
})

An unauthenticated request returns a 401, or redirects to login, depending on content type, before your handler runs. Inside the handler, the user is already there. No async call, no null check, no guessing.

When you need finer control, the authorization middleware composes the same way:

app.get('/admin',  
  requiresAuth(),  
  claimEquals('role', 'admin'),  
  handler  
)

app.get('/reports',  
  requiresAuth(),  
  claimIncludes('permissions', 'read:reports', 'admin:reports'),  
  handler  
)

Notice the pattern. Each check is its own middleware, so you read a route's access rules straight from its definition. The checks read claims, the key-value facts about a user that Auth0 puts in the token.

Managing Sessions with Encrypted Cookies

By default, the session lives in an encrypted cookie. It is stateless, which means there is no session database to run and nothing to coordinate across Cloudflare Workers regions. The SDK decrypts the cookie once per request, about one to two milliseconds, so the user is ready before your handler runs.

The encryption key supports rotation. Pass an array, and the first key encrypts while every key can decrypt, so you can roll keys with zero downtime:

app.use('*', auth0({  
  session: {  
    secret: [process.env.NEW_KEY, process.env.OLD_KEY],  
  },  
}))

If you enrich sessions with larger payloads, cookies have a size limit. For that case, you can plug in a stateful store and keep the data in your own database:

import { SessionStore } from '@auth0/auth0-hono'

const customStore: SessionStore = {  
  async set(name, data, isTransaction, ctx) {  
    await db.sessions.set(data.internal.sid, data)  
  },  
  async get(name, ctx) {  
    return await db.sessions.get(sessionId)  
  },  
  // ... delete, clear  
}

app.use('*', auth0({  
  session: { secret: '...', store: customStore },  
}))

Getting Tokens Without the Refresh Dance

Calling your own API? getAccessToken() returns a valid token and refreshes it for you when it is expired:

app.get('/api/data', requiresAuth(), async (c) => {  
  const { accessToken } = await getAccessToken(c)  
  const res = await fetch('https://api.example.com/data', {  
    headers: { Authorization: `Bearer ${accessToken}` },  
  })  
  return c.json(await res.json())  
})

Here is the part that saves you a real headache: token refresh is deduplicated. If five parallel requests all hit getAccessToken() and the token needs a refresh, the SDK makes one refresh call and the rest await the same promise. You do not get a stampede of refresh requests against your token endpoint, and you did not write a lock to prevent it.

Built for Cloudflare Workers and Everywhere Else

Write your auth code once and run it anywhere Hono runs. The same app deploys to Cloudflare Workers, Node, Bun, Deno, and Vercel Edge without a single platform-specific change.

That portability is the point. You are not locked into one runtime, and you will not rewrite your auth layer if you move from a traditional server to the edge later.

Runtime Level Status
Node.js 18+ Primary Full support, full test coverage
Cloudflare Workers Primary Full support
Bun 1.x+ Secondary Works, best-effort testing
Deno 1.x / 2.x Secondary Works, best-effort testing
Vercel Edge Secondary Works, best-effort testing

The secure path is the default path. The login flow is protected against common attacks, session cookies are encrypted, and your secrets stay on the server, never in a client bundle. Under the hood, the SDK is built on the same Auth0 authentication engine that powers our other server SDKs, so you get the security standards Auth0 maintains across its platform..

See It in Action

The repo ships a full demo, Acme Corp, so you can read working code instead of fragments. It shows the whole flow: an anonymous visitor hits the home page, logs in through Auth0, lands on a protected dashboard, and logs out. You will find it under examples/demo/.

Deploying to Cloudflare Workers

Deploying is the standard Wrangler path. Keep your Auth0 secrets in .dev.vars for local development and as encrypted secrets in production, then:

wrangler deploy

The SDK reads those bindings through Hono's adapter, so there is no extra wiring to make config work on Cloudflare Workers.

Installing the Beta SDK

You have seen the install already. Here is the minimal config to get a real app running:

npm install @auth0/auth0-hono

app.use('*', auth0({  
  domain: 'tenant.auth0.com',  
  clientID: 'abc123',  
  clientSecret: 'secret123',  
  baseURL: 'https://myapp.com',  
}))

What Beta Means Here

Beta means the feature set is production-grade on the primary runtimes (Node and Cloudflare Workers), with full auth flows, typed errors, authorization middleware, and session management. What is still in motion is the shape of the public API. We are settling it before general availability, and would love your feedback by opening an issue or starting a discussion on the repo.

More features are planned to be released soon: token revocation, a JWT API middleware for resource servers, and multi-factor authentication challenge flows.

What Is Next

The upcoming roadmap includes more authentication features like Multiple Custom Domains, Connected Accounts and others, deeper organization support and first-class custom session stores.

For now, the fastest way to see if this fits your stack is to install it, point it at a test tenant, and add auth0() to an app you already have.

Learn More

Get started: npm install @auth0/auth0-hono, see the demo, and read the docs.

About the author

Tushar Pandey

Tushar Pandey

Software Development Engineer II

Tushar Pandey is a Software Development Engineer 2 at Auth0, where he develops and maintains open-source authentication libraries with over 21 million monthly global installs. He builds and ships identity infrastructure across the Next.js, SPA, Node, and Hono ecosystems. His work sits at the intersection of developer experience and security engineering, focused on making authentication reliable at scale, simple to integrate and easy to configure.View profile