Auth Chain Reference
Auth Chain Reference
Runtime authentication flow — How the three-tier auth chain works, feature flags, and the completed Clerk → Better Auth migration.
Table of Contents
- Overview
- Auth Chain Priority
- Sequence Diagram
- Token Disambiguation
- Better Auth (Primary Provider)
- Auth Guards
- When to Use Which Provider
Overview
Every request to the Cloudflare Worker is authenticated through a three-tier chain
implemented in worker/middleware/auth.ts. The chain evaluates providers in strict
priority order and short-circuits on the first successful match:
| Priority | Method | Token Format | Provider |
|---|---|---|---|
| 1 | API Key | blq_... prefix (or legacy abc_...) | Direct PostgreSQL lookup via Hyperdrive |
| 2 | Better Auth | Cookie or bearer session ID | BetterAuthProvider (primary) |
| 3 | Anonymous | No credentials | Falls through to anonymous context |
The chain never throws — all failures are communicated via the response field
on IAuthMiddlewareResult.
Auth Chain Priority
flowchart TD
REQ["Incoming Request"] --> TOKEN{"Extract Bearer<br/>token"}
TOKEN -->|"blq_/abc_ prefix"| APIKEY["API Key Path"]
TOKEN -->|"No token or<br/>non-API-key token"| BA["Better Auth<br/>(Primary Provider)"]
APIKEY --> HASH["SHA-256 hash token"]
HASH --> LOOKUP["Query api_keys table<br/>via Hyperdrive"]
LOOKUP -->|"Found + valid"| TIER["Resolve owner tier<br/>from users table"]
TIER --> AUTH_OK["Authenticated<br/>(api-key method)"]
LOOKUP -->|"Not found / expired / revoked"| REJECT["401 Rejected"]
BA -->|"Valid session<br/>(cookie or bearer)"| ZTA["Run Token Validators<br/>(ZTA checks)"]
ZTA -->|"Pass"| BA_OK["Authenticated<br/>(better-auth method)"]
ZTA -->|"Fail"| REJECT2["401 Rejected"]
BA -->|"No credentials<br/>(no error)"| CLERK_CHECK{"Clerk fallback<br/>enabled?"}
BA -->|"Error<br/>(bad token)"| REJECT3["401 Rejected"]
CLERK_CHECK -->|"Yes + JWT token"| CLERK["Clerk JWT<br/>(Fallback Provider)"]
CLERK_CHECK -->|"No or not JWT"| ANON["Anonymous<br/>(10 req/min)"]
CLERK -->|"Valid JWT"| CLERK_OK["Authenticated<br/>(clerk-jwt method)<br/>Deprecation warning logged"]
CLERK -->|"Invalid JWT"| REJECT4["401 Rejected"]
CLERK -->|"No credentials"| ANON
Sequence Diagram
sequenceDiagram
participant C as Client
participant W as Worker (Hono)
participant AM as authenticateRequestUnified()
participant BA as BetterAuthProvider
participant HD as Hyperdrive
participant DB as Neon PostgreSQL
C->>W: POST /api/compile (Bearer blq_xxx)
W->>AM: authenticate(request, env)
AM->>AM: extractBearerToken() → "blq_xxx"
AM->>AM: isApiKeyToken("blq_xxx") → true
AM->>AM: hashToken("blq_xxx") → SHA-256
AM->>HD: query api_keys WHERE key_hash = $1
HD->>DB: SQL query
DB-->>HD: row { id, user_id, scopes }
HD-->>AM: result
AM->>HD: query users WHERE id = $1 (resolve tier)
HD->>DB: SQL query
DB-->>HD: row { tier: "pro" }
AM-->>W: { context: { authMethod: "api-key", tier: "pro" } }
W-->>C: 200 OK (compiled output)
Note over C,W: --- Different client, Better Auth session ---
C->>W: GET /api/users/me (Cookie: better-auth.session=xxx)
W->>AM: authenticate(request, env)
AM->>AM: extractBearerToken() → null (cookie-based)
AM->>BA: verifyToken(request)
BA->>BA: auth.api.getSession({ headers })
BA-->>AM: { valid: true, providerUserId: "user-uuid" }
AM->>AM: runTokenValidators() → { valid: true }
AM-->>W: { context: { authMethod: "better-auth", tier: "free" } }
W-->>C: 200 OK (user profile)
Token Disambiguation
The auth middleware determines the token type by pattern matching:
/** API keys start with the "blq_" prefix (current) or "abc_" prefix (legacy) */function isApiKeyToken(token: string): boolean { return isApiKey(token); // delegates to api-key-utils.ts}| Token | Pattern | Route |
|---|---|---|
blq_sk_live_xxxx... | Starts with blq_ | → API Key path |
abc_sk_live_xxxx... | Starts with abc_ (legacy) | → API Key path |
sess_abc123xyz | Better Auth session token | → Better Auth (via cookie or bearer plugin) |
| (none) | No Authorization header | → Better Auth (checks cookies) → Anonymous |
Better Auth (Primary Provider)
Implementation: worker/middleware/better-auth-provider.ts
Better Auth is always the first provider consulted for non-API-key requests.
It handles both cookie-based browser sessions and bearer-token API sessions
(via the bearer() plugin).
// worker/lib/auth.ts — createAuth()
export function createAuth(env: Env, baseURL?: string) { const prisma = createPrismaClient(env.HYPERDRIVE!.connectionString);
return betterAuth({ database: prismaAdapter(prisma, { provider: 'postgresql' }), secret: env.BETTER_AUTH_SECRET!, basePath: '/api/auth', baseURL, emailAndPassword: { enabled: true }, user: { additionalFields: { tier: { type: 'string', required: false, defaultValue: 'free', input: false }, role: { type: 'string', required: false, defaultValue: 'user', input: false }, }, }, session: { expiresIn: 60 * 60 * 24 * 7, // 7 days updateAge: 60 * 60 * 24, // refresh within 1 day of expiry }, plugins: [bearer()], });}Session Resolution
Better Auth resolves sessions using auth.api.getSession(), which checks:
Cookie: better-auth.session_token=...— browser sessionsAuthorization: Bearer <session-id>— API sessions (bearer plugin)
Tier Resolution (ZTA)
Tier and role are read from the database on every request — not cached in the
session token. This enables Zero Trust Architecture: revoking a user’s admin role
takes effect immediately without waiting for token expiry.
Auth Guards
The auth middleware provides helper functions for route-level access control:
// Require any authenticated user (rejects anonymous)const authCheck = requireAuth(context);if (authCheck) return authCheck; // 401
// Require minimum tier (e.g., Pro)const tierCheck = requireTier(context, UserTier.Pro);if (tierCheck) return tierCheck; // 403
// Require specific API key scopeconst scopeCheck = requireScope(context, 'compile', 'admin');if (scopeCheck) return scopeCheck; // 403Scope Bypass Rules
| Auth Method | Scope Check |
|---|---|
better-auth | Bypassed — session-authenticated users own the account |
api-key | Enforced — scopes from the api_keys.scopes array |
anonymous | Rejected — anonymous users have no scopes |
Migration Timeline
The Clerk → Better Auth migration follows a phased approach:
gantt
title Clerk → Better Auth Migration
dateFormat YYYY-MM-DD
axisFormat %Y-%m-%d
section Phase 1: Foundation
Prisma adapter + Better Auth setup :done, p1a, 2025-03-01, 14d
Auth chain with dual providers :done, p1b, after p1a, 7d
Feature flags added :done, p1c, after p1b, 3d
section Phase 2: Client Migration
Frontend Better Auth forms :active, p2a, 2025-03-25, 14d
CLI auth migration :p2b, after p2a, 7d
Monitor Clerk fallback usage :p2c, after p2a, 21d
section Phase 3: Clerk Removal
Set DISABLE_CLERK_FALLBACK=true :p3a, after p2c, 3d
Set DISABLE_CLERK_WEBHOOKS=true :p3b, after p3a, 3d
Remove Clerk code + dependencies :p3c, after p3b, 7d
Delete Clerk project :p3d, after p3c, 3d
Current State
- Better Auth is the primary provider and handles all new sign-ups
- Clerk fallback remains enabled for clients not yet migrated
- Clerk webhooks still sync user data to the PostgreSQL
userstable - The deprecation warning in logs tracks how many requests still use Clerk
Monitoring Fallback Usage
To check whether any clients still use Clerk, search Worker logs for:
[auth] Request authenticated via DEPRECATED Clerk fallbackWhen this message stops appearing, it’s safe to proceed to Phase 3.
When to Use Which Provider
| Scenario | Provider | Why |
|---|---|---|
| New API integration | API Key (blq_ prefix) | Scoped, revocable, rate-limited per key |
| Browser app | Better Auth (cookie) | Server-side sessions, no JWTs in localStorage |
| Programmatic API calls | Better Auth (bearer plugin) | Session-based auth without cookies |
| Public/unauthenticated | Anonymous | 10 req/min rate limit, basic features only |
Recommendation
For all new development, use Better Auth:
// Browser: Better Auth handles cookies automatically via /api/auth/*// See: worker/lib/auth.ts → basePath: '/api/auth'
// API: Use the bearer pluginfetch('/api/compile', { headers: { 'Authorization': `Bearer ${sessionToken}`, },});
// Or use an API key for server-to-serverfetch('/api/compile', { headers: { 'Authorization': `Bearer blq_sk_live_...`, },});Further Reading
- Better Auth User Guide — End-user sign-up, sign-in, and account management
- Better Auth Developer Guide — Integration reference for backend and frontend
- Better Auth + Prisma — Prisma adapter configuration
- API Authentication — API key creation and management
- Clerk → Better Auth Migration Guide — Historical reference for the completed migration
- Developer Guide — Full auth integration tutorial
Bloqr AI™ — The privacy you didn't know you needed.
© 2026 Bloqr AI™, a trademark of Bloqr Systems™. Created by Bloqr Systems™, founded by Jayson Knight.
Internet Hygiene (n.) — the ongoing practices that keep your digital life clean, private, and safe.
Our product repos live in the BloqrAI org, part of the Bloqr Systems GitHub Enterprise. Product repos are internal-visibility — enterprise membership is required to view them.