Skip to content

Auth Provider Selection

Auth Provider Selection

The bloqr-backend uses Better Auth exclusively as its authentication provider. There is no runtime provider switching — authentication is handled by the BetterAuthProvider class in worker/middleware/better-auth-provider.ts.

This document explains how the provider was selected, how authentication resolves on every request, and how to extend or replace the provider in the future.


Provider Selection Rationale

FactorBetter Auth
Open sourceYes — MIT licensed
Self-hostedYes — runs entirely within the Cloudflare Worker
No external dependencies at runtimeYes — all auth logic is in-process
Prisma / PostgreSQL supportYes — prismaAdapter with Neon via Hyperdrive
Plugin systemYes — bearer, 2FA, multi-session, admin, and more
Email + passwordYes
OAuth (GitHub, Google)Yes
TOTP / 2FAYes — twoFactor() plugin
Admin management APIYes — admin() plugin

Request Authentication Flow

Every authenticated API request goes through a three-tier chain in worker/middleware/auth.ts:

flowchart TD
    A[Incoming Request] --> B{Bearer token starts\nwith blq_ or abc_?}
    B -- Yes --> C[authenticateApiKey\nHash token → DB lookup]
    C --> D{Valid key?}
    D -- Yes --> E[authMethod = api-key\ntier from user record]
    D -- No --> F[401 Unauthorized]
    B -- No --> G{Session cookie or\nBearer token present?}
    G -- Yes --> H[BetterAuthProvider.verifyToken\nBetter Auth session DB lookup]
    H --> I{Valid session?}
    I -- Yes --> J[authMethod = better-auth\ntier and role from user record]
    I -- No --> K[401 Unauthorized]
    G -- No --> L[authMethod = anonymous\ntier = anonymous]
    E --> M[Route Handler]
    J --> M
    L --> M

authMethod Values

authMethodTriggerNotes
api-keyAuthorization: Bearer blq_... (or legacy abc_...)Long-lived programmatic access keys
better-authSession cookie or Bearer session tokenBrowser sessions and short-lived tokens
anonymousNo credentialsRead-only access to public endpoints

How the Active Provider Is Chosen

There is no dynamic switching at runtime. The Worker instantiates BetterAuthProvider unconditionally:

worker/middleware/auth.ts
const provider = new BetterAuthProvider(env);
const result = await provider.verifyToken(request);

The BetterAuthProvider class wraps the Better Auth library and resolves tier and role from the database on every request (Zero Trust — no JWT claim trust).


Replacing or Extending the Provider

If you need to swap in a different identity provider (Auth0, custom JWKS, etc.), implement the IAuthProvider interface from worker/types.ts:

export interface IAuthProvider {
verifyToken(request: Request): Promise<IAuthProviderResult>;
}

Rules:

  • Return { valid: false } (no error) when no credentials are present in the request.
  • Return { valid: false, error: 'reason' } when credentials are present but invalid.
  • Return { valid: true, userId, tier, role, authMethod, providerUserId } on success.
  • Never throw from verifyToken — return an invalid result instead.

See Better Auth Developer Guide for a full example with Auth0.


What BetterAuthProvider Does

sequenceDiagram
    participant M as Auth Middleware
    participant P as BetterAuthProvider
    participant BA as Better Auth Library
    participant DB as PostgreSQL (via Hyperdrive)

    M->>P: verifyToken(request)
    P->>BA: auth.api.getSession({ headers })
    BA->>DB: SELECT session + user WHERE token = hash(token)
    DB-->>BA: session record + user record
    BA-->>P: { session, user }
    P->>P: resolveTier(user) → UserTier
    P->>P: resolveRole(user) → string
    P-->>M: { valid: true, userId, tier, role, authMethod: "better-auth" }

Tier and role are always resolved from the database user record, not from JWT claims. This is the Zero Trust Architecture (ZTA) principle applied to authentication.


Environment Variables That Control Provider Behaviour

VariableEffect
BETTER_AUTH_SECRETHMAC signing key for session tokens — required
BETTER_AUTH_URLBase URL for OAuth callbacks — required for social sign-in
HYPERDRIVECloudflare Hyperdrive binding for Neon PostgreSQL — required
GITHUB_CLIENT_ID + GITHUB_CLIENT_SECRETEnables GitHub OAuth — optional
GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRETEnables Google OAuth — optional (reserved)

If BETTER_AUTH_SECRET or HYPERDRIVE is missing, the Worker throws a WorkerConfigurationError at startup.