Package Exports
- bezzie
Readme
Bezzie
Bezzie — your BFF's BFF. Handles the Backend for Frontend OAuth pattern so you don't have to.
A BFF (Backend for Frontend) OAuth 2.0 auth library for Cloudflare Workers.
Implements the OAuth 2.0 for Browser-Based Apps (BCP212) pattern — JWTs never touch the browser. The BFF owns the OAuth flow and issues a session cookie to the frontend instead.
npm install bezzieGet started in 5 minutes
1. Install:
npm install bezzie2. Add a KV namespace to wrangler.toml:
[[kv_namespaces]]
binding = "SESSION_KV"
id = "<your-kv-namespace-id>"3. Add your client secret:
wrangler secret put AUTH0_CLIENT_SECRET4. Wire it up:
import { createBezzie, providers, cloudflareKV } from 'bezzie'
const auth = createBezzie({
...providers.auth0('your-tenant.auth0.com'),
clientId: 'xxx',
clientSecret: env.AUTH0_CLIENT_SECRET,
adapter: cloudflareKV(env.SESSION_KV),
baseUrl: 'https://app.yourproject.com',
})
app.route('/auth', auth.routes())
app.use('/api/*', auth.middleware())5. Protect a route:
app.get('/api/me', (c) => c.json(c.var.user))Done. Your app now has BCP212-compliant BFF auth.
Why
Most OAuth libraries hand tokens directly to the browser. BCP212 says you shouldn't — it's a significant attack surface. Bezzie keeps tokens server-side in Cloudflare KV and gives the browser a session cookie instead.
There's no open source library for this specific combination (BFF OAuth on Cloudflare Workers). The closest alternatives are Duende BFF (.NET) and @auth0/nextjs-auth0 — both tied to specific frameworks.
Usage
import { createBezzie, providers, cloudflareKV } from 'bezzie'
const auth = createBezzie({
...providers.auth0('your-tenant.auth0.com'),
clientId: 'xxx',
clientSecret: env.AUTH0_CLIENT_SECRET,
audience: 'https://api.yourproject.com',
adapter: cloudflareKV(env.SESSION_KV),
baseUrl: 'https://app.yourproject.com',
})
// Mount auth routes
app.route('/auth', auth.routes())
// Protect API routes
app.use('/api/*', auth.middleware())This gives you:
| Route | Description |
|---|---|
GET /auth/login |
Redirects to provider, initiates Authorization Code + PKCE flow. Supports returnTo query param for post-login redirect. |
GET /auth/callback |
Exchanges code for tokens, stores session in KV, sets cookie. |
POST /auth/logout |
Clears session, clears cookie, redirects to provider logout. |
Accessing User Identity
After auth.middleware(), downstream handlers can access the user identity and the current access token via c.var:
app.get('/api/me', (c) => {
const user = c.var.user
const token = c.var.accessToken
return c.json({ user })
})Forwarding Upstream
The accessToken on the context is intended for the app to forward to an upstream service (e.g., a Spring Boot API or any other microservice), since Bezzie doesn't mutate request headers directly.
app.all('/api/proxy/*', async (c) => {
const url = new URL(c.req.url)
const target = `https://api.upstream.com${url.pathname}${url.search}`
return fetch(target, {
method: c.req.method,
headers: {
...c.req.header(),
'Authorization': `Bearer ${c.var.accessToken}`
},
body: c.req.raw.body
})
})How It Works
Login Flow
Browser → BFF /auth/login → Auth0 (Authorization Code + PKCE)
│
code returned
│
BFF exchanges code → tokens stored in KV
BFF issues HttpOnly session cookie → BrowserPer-Request Flow
- Browser sends request to BFF with session cookie
- BFF looks up session in KV, retrieves access token
- BFF validates JWT (via JWKS, using Web Crypto API)
- If expired, BFF uses refresh token to get a new one and updates KV
- BFF forwards request upstream with
Authorization: Bearer <token>
Session Storage
Sessions are stored in Cloudflare KV:
sessionId → { accessToken, refreshToken, expiresAt, user }KV TTL is aligned with the refresh token lifetime. When the refresh token expires, the user must log in again.
Adapters
Bezzie supports multiple session storage backends:
Cloudflare KV
Recommended for production on Cloudflare Workers.
import { cloudflareKV } from 'bezzie'
// ...
adapter: cloudflareKV(env.SESSION_KV)Redis (Upstash)
Good for cross-region session consistency.
import { RedisAdapter } from 'bezzie'
// ...
adapter: new RedisAdapter({ url: env.REDIS_URL, token: env.REDIS_TOKEN })Memory
Useful for local development and testing. Do not use in production.
import { MemoryAdapter } from 'bezzie'
// ...
adapter: new MemoryAdapter()Configuration
| Option | Type | Description |
|---|---|---|
issuer |
string |
Your OIDC provider issuer URL (e.g. https://tenant.auth0.com) |
clientId |
string |
OAuth client ID |
clientSecret |
string |
OAuth client secret — keep in Workers secrets |
audience |
string |
API audience identifier |
adapter |
SessionAdapter |
Session adapter (e.g. cloudflareKV(env.SESSION_KV)) |
baseUrl |
string |
Base URL of your application (used for callback and redirects) |
providerHints |
object |
Optional tweaks for specific providers (logoutUrl, tokenEndpoint) |
Cloudflare Setup
Add a KV namespace to your wrangler.toml:
[[kv_namespaces]]
binding = "SESSION_KV"
id = "<your-kv-namespace-id>"Add your client secret as a Workers secret:
wrangler secret put AUTH0_CLIENT_SECRETStack
| Component | Choice |
|---|---|
| Runtime | Cloudflare Workers |
| Router | Hono |
| OAuth | oauth4webapi (spec-compliant, no Node.js deps) |
| Session storage | Cloudflare KV |
Status
v0.1.0 — pre-release
License
MIT