Skip to content

Latest commit

 

History

History
239 lines (185 loc) · 7.29 KB

File metadata and controls

239 lines (185 loc) · 7.29 KB

Better Auth Cloudflare Hono Example

This example demonstrates how to integrate Better Auth with Hono on Cloudflare Workers using the better-auth-cloudflare plugin.

Features

  • 🚀 Hono Framework: Lightning-fast web framework for Cloudflare Workers
  • 🗄️ D1 Database Integration: SQLite database via Cloudflare D1
  • 🔌 KV Storage Integration: Session caching via Cloudflare KV
  • 📍 Automatic Geolocation Tracking: Enriches sessions with location data
  • 🌐 Cloudflare IP Detection: Automatic IP address detection
  • 👤 Anonymous Authentication: Built-in anonymous user authentication
  • 🔐 Session Management: Secure session handling with geolocation

Getting Started

Prerequisites

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

Installation

  1. Navigate to this directory:
cd examples/hono
  1. Install dependencies:
pnpm install
  1. Configure your Cloudflare bindings in wrangler.toml:
[[d1_databases]]
binding = "DATABASE"
database_name = "your-database-name"
database_id = "your-database-id"

[[kv_namespaces]]
binding = "KV"
id = "your-kv-namespace-id"

Database Setup

  1. Create a D1 database:
wrangler d1 create your-database-name
  1. Update the database_id in wrangler.toml with the ID from the previous command.

  2. Create a KV namespace:

wrangler kv namespace create "KV"
  1. Update the KV id in wrangler.toml with the ID from the previous command.

  2. Apply database migrations:

pnpm run db:migrate:prod

Deployment

Deploy to Cloudflare Workers:

pnpm run deploy

Project Structure

src/
├── auth/
│   └── index.ts          # Better Auth configuration
├── db/
│   ├── index.ts          # Database exports
│   ├── schema.ts         # Combined schema
│   └── auth.schema.ts    # Generated auth schema
├── env.d.ts              # TypeScript environment types
└── index.ts              # Hono application

drizzle/                  # Database migrations
wrangler.toml            # Cloudflare Worker configuration

Available Scripts

Authentication Scripts

  • pnpm run auth:generate - Generate auth schema from Better Auth config
  • pnpm run auth:format - Format the generated auth schema
  • pnpm run auth:update - Generate and format auth schema

Database Scripts

  • pnpm run db:generate - Generate new database migrations
  • pnpm run db:migrate:dev - Apply migrations to local D1 database
  • pnpm run db:migrate:prod - Apply migrations to production D1 database
  • pnpm run db:studio:dev - Open Drizzle Studio for local database
  • pnpm run db:studio:prod - Open Drizzle Studio for production database

Development Scripts

  • pnpm run dev - Start development server
  • pnpm run deploy - Deploy to Cloudflare Workers
  • pnpm run cf-typegen - Generate Cloudflare binding types

Usage

API Endpoints

  • GET / - Demo page with anonymous authentication UI
  • GET /health - Health check endpoint
  • GET /protected - Protected route demo
  • ALL /api/auth/* - All Better Auth routes (handled by better-auth)
  • POST /api/auth/sign-in/anonymous - Anonymous login
  • POST /api/auth/sign-out - Sign out
  • GET /api/auth/get-session - Get current session
  • GET /api/auth/cloudflare/geolocation - Get geolocation data

Geolocation Tracking

When geolocationTracking is enabled, user sessions automatically include:

  • 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 & longitude - Coordinates

Configuration

Environment Variables

The application uses Cloudflare bindings defined in wrangler.toml:

interface CloudflareBindings {
    DATABASE: D1Database;
    KV: KVNamespace;
}

Better Auth Configuration

The auth configuration in src/auth/index.ts uses a simplified single-function approach that handles both CLI schema generation and runtime scenarios:

import type { D1Database, IncomingRequestCfProperties } from "@cloudflare/workers-types";
import { betterAuth } from "better-auth";
import { withCloudflare } from "better-auth-cloudflare";
import { anonymous } from "better-auth/plugins";
import { drizzleAdapter } from "@better-auth/drizzle-adapter";
import { drizzle } from "drizzle-orm/d1";
import { schema } from "../db";
import type { CloudflareBindings } from "../env";

// Single auth configuration that handles both CLI and runtime scenarios
function createAuth(env?: CloudflareBindings, cf?: IncomingRequestCfProperties, baseURL?: string) {
    // Use actual DB for runtime, empty object for CLI
    const db = env ? drizzle(env.DATABASE, { schema, logger: true }) : ({} as any);

    return betterAuth({
        baseURL,
        ...withCloudflare(
            {
                autoDetectIpAddress: true, // Auto-detect IP from Cloudflare headers
                geolocationTracking: true, // Track geolocation in sessions
                cf: cf || {},
                d1: env
                    ? {
                          db,
                          options: {
                              usePlural: true,
                              debugLogs: true,
                          },
                      }
                    : undefined,
                kv: env?.KV,
            },
            {
                emailAndPassword: {
                    enabled: true,
                },
                plugins: [anonymous()], // Enable anonymous authentication
                rateLimit: {
                    enabled: true,
                    window: 60, // Minimum KV TTL is 60s
                    max: 100, // reqs/window
                    customRules: {
                        // https://github.com/better-auth/better-auth/issues/5452
                        "/sign-in/email": {
                            window: 60,
                            max: 100,
                        },
                        "/sign-in/social": {
                            window: 60,
                            max: 100,
                        },
                    },
                },
            }
        ),
        // Only add database adapter for CLI schema generation
        ...(env
            ? {}
            : {
                  database: drizzleAdapter({} as D1Database, {
                      provider: "sqlite",
                      usePlural: true,
                      debugLogs: true,
                  }),
              }),
    });
}

// Export for CLI schema generation
export const auth = createAuth();

// Export for runtime usage
export { createAuth };

The baseURL is derived from each incoming request in the Hono middleware (new URL(c.req.url).origin) and passed to createAuth. On Cloudflare Workers, request.url reflects the actual URL the client connected to — Cloudflare's edge routes requests to your worker based on DNS and route configuration, not the HTTP Host header alone. Alternatively, you can set the BETTER_AUTH_URL environment variable and omit the baseURL parameter.