better-auth-cloudflare

repository·main·Indexed 20 days ago

https://github.com/zpg6/better-auth-cloudflare

An integration layer for Better Auth designed for the Cloudflare ecosystem. It enables the use of Cloudflare Workers, D1, Hyperdrive, KV, R2, and geolocation services within authentication workflows. Includes a CLI for project scaffolding via `generate` and schema updates via `migrate`, supporting Hono and Next.js (OpenNext.js) templates.

Tokens
27.8K
Snippets
72
Records
91
Agent score
68%

What's inside better-auth-cloudflare

  1. How geolocation tracking works in Better Auth Cloudflare

    main

    When geolocationTracking is set to true within the withCloudflare configuration, the plugin automatically enriches user sessions with location data derived from Cloudflare's edge headers.

    This data is available in the session and includes:

    • timezone: User's timezone
    • city: User's city
    • country: User's country
    • region: User's region/state
    • regionCode: Region code
    • colo: Cloudflare colo data center
    • latitude: Latitude coordinates
    • longitude: Longitude coordinates
  2. Use native D1 bindings without Drizzle

    main

    If you prefer not to use Drizzle ORM, you can pass a D1 binding directly to withCloudflare using the d1Native key. This uses Better Auth's built-in Kysely D1 dialect.

    Trade-offs:

    • Pros: Smaller bundle size, simpler setup.
    • Cons: No type-safe queries via Drizzle, manual SQL/CLI schema management required.

    Example:

    const auth = betterAuth({
        ...withCloudflare({
            d1Native: env.DATABASE, // D1Database binding from wrangler.toml
            kv: env.KV,
        }, {
            // auth options
        }),
    });
    import { betterAuth } from "better-auth";
    import { withCloudflare } from "better-auth-cloudflare";
    
    const auth = betterAuth({
        ...withCloudflare(
            {
                d1Native: env.DATABASE, 
                kv: env.KV,
            },
            {
                // your auth options...
            }
        ),
    });
  3. Understand `withCloudflare` override behavior

    main

    The withCloudflare wrapper returns a merged configuration where certain keys take precedence over your provided authOptions. You should omit these keys from your authOptions to avoid confusion:

    • database: Set automatically from your d1, d1Native, postgres, or mysql option.
    • secondaryStorage: Set to createKVStorage(kv) if kv is provided.
    • plugins: The cloudflare() plugin is automatically prepended to your plugins array.
    • advanced: Merges your options with IP detection headers if autoDetectIpAddress is enabled.
    • session: Merges your options and forces storeSessionInDatabase: true if geolocationTracking is enabled.
  4. How R2 lifecycle hooks work

    main

    Lifecycle hooks allow you to inject business logic at specific points in the file lifecycle. Returning null from a before hook will block the operation (upload, download, or delete).

    Available hook categories:

    • upload: Contains before and after hooks.
    • download: Contains before and after hooks.
    • delete: Contains before and after hooks.
    const r2Config = {
        bucket: env.R2_BUCKET,
        hooks: {
            upload: {
                before: async (file, ctx) => { /* logic */ },
                after: async (file, ctx) => { /* logic */ },
            },
            download: {
                before: async (file, ctx) => { /* logic */ },
            },
            delete: {
                before: async (file, ctx) => { /* logic */ },
            },
        },
    };
  5. Use Cloudflare KV for Secondary Storage

    main

    Passing kv to withCloudflare enables Better Auth Secondary Storage (used for rate limiting, session caching, and verification tokens) backed by Cloudflare KV.

    KV TTL Limitation

    Cloudflare KV enforces a minimum TTL of 60 seconds. createKVStorage automatically clamps lower values and logs a warning.

    Important: You must ensure your rate limit window is at least 60 seconds. Because some built-in sign-in endpoints have default windows lower than 60s, you should explicitly override them to prevent KV write errors.

    withCloudflare(
        {
            d1: { db, options: { usePlural: true } },
            kv: env.KV,
            cf: request.cf,
        },
        {
            rateLimit: {
                enabled: true,
                window: 60, // Must be >= 60 when using KV
                max: 100,
                customRules: {
                    "/sign-in/email": { window: 60, max: 5 },
                    "/sign-in/social": { window: 60, max: 5 },
                },
            },
        }
    );
  6. Set up Better Auth with Hono on Cloudflare Workers

    main

    This guide outlines how to set up a Hono application using better-auth-cloudflare with D1 for database storage and KV for session caching.

    Prerequisites

    • Node.js 18+ and pnpm
    • Cloudflare account with Workers and D1 enabled
    • Wrangler CLI installed: npm install -g wrangler

    Installation Steps

    1. Install dependencies: Navigate to the project directory and run pnpm install.
    2. Configure Bindings: In your wrangler.toml, define the DATABASE (D1) and KV (KV Namespace) bindings:
      [[d1_databases]]
      binding = "DATABASE"
      database_name = "your-database-name"
      database_id = "your-database-id"
      
      [[kv_namespaces]]
      binding = "KV"
      id = "your-kv-namespace-id"
    3. Initialize D1: Run wrangler d1 create your-database-name and update your wrangler.toml with the resulting database_id.
    4. Initialize KV: Run wrangler kv namespace create "KV" and update your wrangler.toml with the resulting id.
    5. Migrations: Apply database migrations using pnpm run db:migrate:prod.
    6. Deploy: Deploy the worker using pnpm run deploy.
    cd examples/hono
    pnpm install
    wrangler d1 create your-database-name
    wrangler kv namespace create "KV"
    pnpm run db:migrate:prod
    pnpm run deploy
  7. Install better-auth-cloudflare

    main

    Install the package using your preferred package manager:

    npm install better-auth-cloudflare
    # or
    yarn add better-auth-cloudflare
    # or
    pnpm add better-auth-cloudflare
    # or
    bun add better-auth-cloudflare
    npm install better-auth-cloudflare
  8. Set up Better Auth API routes

    main

    You must create API routes to handle authentication requests. The auth.handler(req) method processes the incoming request.

    Example (Next.js App Router style):

    import { initAuth } from "@/auth";
    
    export async function POST(req: Request) {
        const auth = await initAuth();
        return auth.handler(req);
    }
    
    export async function GET(req: Request) {
        const auth = await initAuth();
        return auth.handler(req);
    }
    // Example: src/app/api/auth/[...all]/route.ts
    import { initAuth } from "@/auth";
    
    export async function POST(req: Request) {
        const auth = await initAuth();
        return auth.handler(req);
    }
    
    export async function GET(req: Request) {
        const auth = await initAuth();
        return auth.handler(req);
    }
  9. Configure better-auth-cloudflare with OpenNext.js

    main

    When using OpenNext.js on Cloudflare Workers, you must use an asynchronous builder pattern and a singleton to handle async database initialization and Cloudflare context. This prevents issues with getCloudflareContext() being unavailable during CLI execution and ensures a single auth instance across serverless functions.

    Implementation Pattern

    1. Define an authBuilder function that is async.
    2. Use getCloudflareContext() to access cf and env.
    3. Use a singleton authInstance and an initAuth() function to retrieve the instance.
    4. Provide a separate, static auth export for the Better Auth CLI to perform schema generation without requiring a live Cloudflare environment.
    import { getCloudflareContext } from "@opennextjs/cloudflare";
    import { betterAuth } from "better-auth";
    import { withCloudflare } from "better-auth-cloudflare";
    import { drizzleAdapter } from "@better-auth/drizzle-adapter";
    import { anonymous, openAPI } from "better-auth/plugins";
    import { getDb } from "../db";
    
    async function authBuilder() {
        const dbInstance = await getDb();
        const cfCtx = getCloudflareContext();
        return betterAuth({
            ...withCloudflare(
                {
                    autoDetectIpAddress: true,
                    geolocationTracking: true,
                    cf: cfCtx.cf,
                    d1: {
                        db: dbInstance,
                        options: {
                            usePlural: true,
                            debugLogs: true,
                        },
                    },
                    kv: cfCtx.env.KV,
                },
                {
                    baseURL: cfCtx.env.BETTER_AUTH_URL,
                    trustedOrigins: (cfCtx.env.BETTER_AUTH_TRUSTED_ORIGINS ?? "").split(",").filter(Boolean),
                    rateLimit: {
                        enabled: true,
                        window: 60,
                        max: 100,
                    },
                    plugins: [openAPI(), anonymous()],
                }
            ),
        });
    }
    
    let authInstance: Awaited<ReturnType<typeof authBuilder>> | null = null;
    
    export async function initAuth() {
        if (!authInstance) {
            authInstance = await authBuilder();
        }
        return authInstance;
    }
  10. Scaffold a new project with `generate`

    main

    Use the generate command to create a complete Better Auth Cloudflare project. This command scaffolds Hono or Next.js (OpenNext.js) applications, sets up TypeScript configurations, package.json scripts, API routes, and database adapters. It also handles Cloudflare resource creation (D1, KV, R2, Hyperdrive) and runs initial migrations.

    Interactive Mode: Run the command without arguments to be prompted for configuration:

    npx @better-auth-cloudflare/cli generate

    Non-interactive Mode: Pass arguments to automate the setup:

    npx @better-auth-cloudflare/cli generate \
      --app-name=my-auth-app \
      --template=hono \
      --database=d1 \
      --kv=true \
      --r2=false \
      --apply-migrations=dev
    npx @better-auth-cloudflare/cli generate --app-name=my-auth-app --template=hono --database=d1 --kv=true --r2=false --apply-migrations=dev
  11. Update auth schema with `migrate`

    main

    When you modify your authentication configuration, use the migrate command to streamline schema updates. It automatically detects your database configuration from wrangler.toml and performs the following workflow:

    1. Generates the updated auth schema.
    2. Creates Drizzle migrations.
    3. Optionally applies the migrations.

    Interactive Mode:

    npx @better-auth-cloudflare/cli migrate

    Non-interactive Mode:

    npx @better-auth-cloudflare/cli migrate --migrate-target=dev

    Supported Targets:

    • D1 databases: Supports dev or remote migration targets.
    • Hyperdrive databases: Displays an informational message (migrations are not applied via this command for Hyperdrive).
    • Multiple databases: If multiple D1 databases are detected, you will be prompted to choose which one to migrate.
  12. Generate Better Auth database schema using the CLI

    main

    The Better Auth CLI can automatically generate the Drizzle schema required for authentication tables (users, sessions, etc.). This schema should be imported into your main Drizzle schema file to ensure all tables are managed together.

    Command: npx @better-auth/cli@latest generate

    Recommended usage with explicit paths: npx @better-auth/cli@latest generate --config src/auth/index.ts --output src/db/auth.schema.ts -y

    Workflow:

    1. Run the command to create src/db/auth.schema.ts.
    2. Import authSchema into your main src/db/schema.ts.
    3. Use Drizzle Kit to create and apply migrations to your database.
    npx @better-auth/cli@latest generate --config src/auth/index.ts --output src/db/auth.schema.ts -y