Auth
Better Auth on Cloudflare — authentication, sessions, organizations, and plugins for your Quickback API.
Quickback Stack uses Better Auth for authentication, running on Cloudflare Workers with D1 as the session store.
Overview
Better Auth provides:
- Email/password authentication
- Session management with cookies
- Multi-tenant organizations with roles
- Plugin ecosystem for passwordless auth, passkeys, and more
Configuration
Auth is configured in your quickback.config.ts:
import { defineAuth, defineConfig, defineRuntime, defineDatabase } from "@quickback/compiler";
export default defineConfig({
name: "my-app",
providers: {
runtime: defineRuntime("cloudflare"),
database: defineDatabase("cloudflare-d1"),
auth: defineAuth("better-auth", {
emailAndPassword: { enabled: true },
plugins: ["emailOtp", "passkey", "magicLink"],
}),
},
});Auth Base Path
All Better Auth routes are served under:
/auth/v1/*Common endpoints:
POST /auth/v1/sign-in/email— Email/password sign inPOST /auth/v1/sign-up/email— Create accountGET /auth/v1/get-session— Get current sessionPOST /auth/v1/sign-out— Sign out
Organization Roles
| Role | Description |
|---|---|
owner | Full access — can delete the organization and transfer ownership |
admin | Full access — can manage members and resources, cannot delete the organization |
member | Standard access — read and limited write, cannot delete or manage members |
These are Better Auth's built-in organization roles — no configuration needed. The creatorRole defaults to owner.
Tip: Account UI's role picker uses these exact three roles. Use
["owner", "admin", "member"]in your Access rules so generated projects plug into Better Auth and Account UI seamlessly.
Roles are used throughout the security layers — in Access rules and Firewall owner checks. Roles are an API gate: they do not reach SQL. Generated Row Level Security is a backstop that fences tenant scope, soft delete and lane admission only, and no generated policy body names a role. Better Auth's own API is the gate for its tables.
Next Steps
- Plugins — Email OTP, passkeys, magic links, and more
- Security — Cookies, rate limiting, cross-domain auth
Compiler-managed auth versions and upgrades
The compiler pins Better Auth, its schema CLI, and the generated companion
packages to 1.7.3. Recompile and install the emitted dependencies together;
remove older Better Auth overrides from build.dependencies. An incompatible
override is rejected because it would run a different auth version from the
one that generated your schema.
For an existing project, retain its quickback/drizzle migration history and
use the updated CLI when recompiling. The CLI generates the upgrade locally;
migration history stays on your machine. Apply the newly generated auth
migration before starting the updated Worker. Existing migration SQL is
preserved.
Upgrading from 1.6
The upgrade adds the current JWT/OAuth schema, including JWKS alg/crv and
resource-bound grants. The account table is unchanged — accounts are still
keyed by (providerId, accountId), so there is no identity backfill and no
credential reset.
Existing OAuth grants without a recorded resource set are revoked during the
upgrade; clients must authorize again. Client registrations and ordinary
sessions are retained. The compiler maps its audience configuration into the
1.7 resources option and preserves the previous global resource allowlist
for clients (enforcePerClientResources: false). Authorization-code and refresh
grants still bind resources and reject widening to an unauthorized resource.
Projects opting into per-client resource restrictions must link their clients
to resources using the OAuth provider's resource administration APIs.
Projects compiled between 2026-09-05 and the 1.7.3 pin
Better Auth 1.7.0–1.7.2 briefly keyed accounts by an issuer column with a
unique (issuer, accountId) index. 1.7.3 restored the 1.6 account schema, so
only projects compiled during that window carry the extra column.
Recompiling emits one appended migration that drops the identity index and then
the issuer column — index first, because SQLite refuses to drop an indexed
column. It is generated from the schema diff, so a project that never had
issuer gets nothing, and prior migration SQL is untouched. No backfill is
needed in either direction: issuer was always derived from providerId, and
1.7.3 never reads it.
Apply it before the updated Worker serves traffic. A leftover NOT NULL
issuer column is an outage on 1.7.3 — Better Auth validates its schema at
init and refuses a required column it never writes.
Generated projects keep that validation on at runtime
(advanced.database.validateSchema: !!env in src/lib/auth.ts). It is off in
exactly one case: when the schema CLI imports auth.ts there are no bindings,
so the adapter holds a stub with no tables to check and the validator would
report every table as missing.
The bundled Account UI uses signIn.social and linkSocial for generic OAuth
providers, including Cloudflare. Their callback is
/auth/v1/oauth2/callback/cloudflare; update the registered redirect URI when
upgrading an existing Cloudflare login integration.