使用原始sqlite
https://better-auth.com/docs/plugins/api-key#update-an-api-key
注意第三步,由于我们后端使用了Hono,这里新建一个auth-cli.ts配合auth生成schema,如下:
import {betterAuth} from "better-auth"import {dash} from "@better-auth/infra"import {apiKey} from "@better-auth/api-key";import Database from "better-sqlite3";const auth = betterAuth( { database: new Database("database.sqlite"), secrets: [ { version: 1, value: "QDJZgrf8HvKYRwh39ZfpniinZi5vfCj0" } ], emailAndPassword: { enabled: true, }, plugins: [ dash(), apiKey() ] } );export default auth;安装better-sqlite3
npm install better-sqlite3npm install -D @types/better-sqlite3生成schema
npx auth generate --config ./src/lib/auth-cli.ts2026-07-15T06:14:58.021Z WARN **[Better Auth]:** [better-auth] Base URL is not set. Set the baseURL option or BETTER_AUTH_URL env, or use a dynamic baseURL with allowedHosts for multi-host setups. Without it the origin is derived from the incoming request, and callbacks and redirects may not work correctly.✔ **Do you want to generate the schema to** **./better-auth_migrations/2026-07-15T06-14-58.034Z.sql****?** … yes🚀 Schema was generated successfully!• 最好能够添加“if not exists”
sed -i '' 's/create table /create table if not exists /g' ./better-auth_migrations/2026-07-15T06-14-58.034Z.sql查看schema内容
create table if not exists "user" ("id" text not null primary key, "name" text not null, "email" text not null unique, "emailVerified" integer not null, "image" text, "createdAt" date not null, "updatedAt" date not null);create table if not exists "session" ("id" text not null primary key, "expiresAt" date not null, "token" text not null unique, "createdAt" date not null, "updatedAt" date not null, "ipAddress" text, "userAgent" text, "userId" text not null references "user" ("id") on delete cascade);create table if not exists "account" ("id" text not null primary key, "accountId" text not null, "providerId" text not null, "userId" text not null references "user" ("id") on delete cascade, "accessToken" text, "refreshToken" text, "idToken" text, "accessTokenExpiresAt" date, "refreshTokenExpiresAt" date, "scope" text, "password" text, "createdAt" date not null, "updatedAt" date not null);create table if not exists "verification" ("id" text not null primary key, "identifier" text not null, "value" text not null, "expiresAt" date not null, "createdAt" date not null, "updatedAt" date not null);create table if not exists "apikey" ("id" text not null primary key, "configId" text not null, "name" text, "start" text, "referenceId" text not null, "prefix" text, "key" text not null, "refillInterval" integer, "refillAmount" integer, "lastRefillAt" date, "enabled" integer, "rateLimitEnabled" integer, "rateLimitTimeWindow" integer, "rateLimitMax" integer, "requestCount" integer, "remaining" integer, "lastRequest" date, "expiresAt" date, "createdAt" date not null, "updatedAt" date not null, "permissions" text, "metadata" text);create index "session_userId_idx" on "session" ("userId");create index "account_userId_idx" on "account" ("userId");create index "verification_identifier_idx" on "verification" ("identifier");create index "apikey_configId_idx" on "apikey" ("configId");create index "apikey_referenceId_idx" on "apikey" ("referenceId");create index "apikey_key_idx" on "apikey" ("key");备份线上数据库
wrangler d1 export better_auth_cloudflare_demo --remote --output="./backup.sql"迁移schema到线上
wrangler d1 execute better_auth_cloudflare_demo --remote --file=./better-auth_migrations/2026-07-15T06-14-58.034Z.sql▲ [WARNING] **Proxy environment variables detected. We'll use your proxy for fetch requests.** ⛅️ wrangler 4.106.0 (update available 4.111.0)───────────────────────────────────────────────**Resource location:** remote ✔ **⚠️ This process may take some time, during which your D1 database will be unavailable to serve queries.** **Ok to proceed?** … yes🌀 Executing on remote database better_auth_cloudflare_demo (25b30dd3-b35d-45fa-9fa9-7c2af8240024):🌀 To execute on your local development database, remove the --remote flag from your wrangler command.Note: if the execution fails to complete, your DB will return to its original state and you can safely retry.├ 🌀 Uploading 25b30dd3-b35d-45fa-9fa9-7c2af8240024.ecd456601eeae9cd.sql│ 🌀 Uploading complete.│🌀 Starting import...🌀 Processed 11 queries.🚣 Executed 11 queries in 7.60ms (27 rows read, 16 rows written) Database is currently at bookmark 00000011-00000006-000050a9-745ebcde7be90d22931c142a684adcd6.┌────────────────────────┬───────────┬──────────────┬────────────────────┐│ Total queries executed │ Rows read │ Rows written │ Database size (MB) │├────────────────────────┼───────────┼──────────────┼────────────────────┤│ 11 │ 27 │ 16 │ 0.09 │└────────────────────────┴───────────┴──────────────┴────────────────────┘测试
• 注册
curl -X POST http://localhost:8787/api/auth/sign-up/email \ -H 'Content-Type: application/json' \ -d '{"email":"test1@example.com","password":"12345678","name":"test"}'{"token":"TW2r49vYFVrXjYJKrHimsHEvblJXyjkb","user":{"name":"test","email":"test1@example.com","emailVerified":false,"image":null,"createdAt":"2026-07-16T02:49:58.678Z","updatedAt":"2026-07-16T02:49:58.680Z","id":"yfpSbmt6ZNLMmxI2mplC9IcM30UeZYEt"},"apiKey":{"id":"8BPh55jSSjpG0pkwKgD5dFaA4N1nBkFa","name":"test 的初始密钥","secretKey":"jHvNYYarROHBeTIyRrvzbnrxOfQTLkMvHBbjSZfoyBnSxXIlowCffOXvOqJDPkzt","expiresAt":null}}• 访问,不携带secretKey
curl -X GET http://localhost:8787/api/user/profile{"error":"unauthorized"}• 访问,携带secretKey
curl -X GET http://localhost:8787/api/user/profile \ -H "Authorization: Bearer jHvNYYarROHBeTIyRrvzbnrxOfQTLkMvHBbjSZfoyBnSxXIlowCffOXvOqJDPkzt"{"id":"yfpSbmt6ZNLMmxI2mplC9IcM30UeZYEt","name":"test","email":"test1@example.com","emailVerified":false,"image":null,"createdAt":"2026-07-16T02:49:58.678Z","updatedAt":"2026-07-16T02:49:58.680Z"}• 访问,携带错误的secretKey,比如将最后一个字母修改掉
curl -X GET http://localhost:8787/api/user/profile \ -H "Authorization: Bearer jHvNYYarROHBeTIyRrvzbnrxOfQTLkMvHBbjSZfoyBnSxXIlowCffOXvOqJDPkza"{"success":false,"message":"未授权:无效或过期的 API Key"}使用Drizzle方式
安装
npm install @better-auth/drizzle-adapternpm install drizzle-ormnpm install -D drizzle-kit tsx代码架构
.├── drizzle.config.ts├── package-lock.json├── package.json├── README.md├── src│ ├── constants.ts│ ├── db│ │ ├── db.ts│ │ ├── drizzle│ │ │ ├── 0000_gifted_raza.sql│ │ │ └── meta│ │ │ ├── _journal.json│ │ │ └── 0000_snapshot.json│ │ └── schema.ts│ ├── index.ts│ ├── lib│ │ ├── auth-cli.ts│ │ └── auth.ts│ ├── middleware│ │ ├── apiKey.ts│ │ ├── cors.ts│ │ └── index.ts│ ├── routes│ │ ├── auth.ts│ │ ├── index.ts│ │ └── user.ts│ └── types.ts├── tsconfig.json├── worker-configuration.d.ts└── wrangler.jsonc配置auth
auth
import { betterAuth } from "better-auth"import { dash } from "@better-auth/infra"import { apiKey } from "@better-auth/api-key";import { drizzleAdapter } from "better-auth/adapters/drizzle";import { createDb, type Database } from "../db/db";import * as schema from "../db/schema";import { TRUSTED_ORIGINS } from "../constants";export const createAuth = (env: Cloudflare.Env, existDb?: Database) => { const db = existDb ?? createDb(env.better_auth_cloudflare_demo); return betterAuth({ database: drizzleAdapter(db, { provider: "sqlite", schema }), emailAndPassword: { enabled: true, }, secrets: [ { version: 1, value: env.SECRET_KEY_V1 }, ], basePath: "/api/auth", baseURL: env.BETTER_AUTH_URL, trustedOrigins: [...TRUSTED_ORIGINS], rateLimit: { enabled: true, timeWindow: 60, maxRequests: 10, }, logger: { disabled: true }, plugins: [ apiKey({ enableSessionForAPIKeys: true, //用 session 方式进行中间件校验 permissions: { defaultPermissions: { files: ["read"], users: ["read"], }, }, rateLimit: { enabled: true, timeWindow: 60, maxRequests: 100 } }) ] }); };//获取动态 Auth 实例的类型 export type AuthInstance = ReturnType<typeof createAuth>;//推导 Session 和 User 的类型 export type SessionUser = AuthInstance["$Infer"]["Session"]["user"];//如果您在下游路由需要拿到 API Key 的元数据(如创建时间、权限列表、所属用户ID等),这样推导: export type ApiKeyInfo = Awaited<ReturnType<AuthInstance["api"]["verifyApiKey"]>>["key"];auth-cli
import {betterAuth} from "better-auth"import {dash} from "@better-auth/infra"import {apiKey} from "@better-auth/api-key";import {drizzleAdapter} from "better-auth/adapters/drizzle";import {drizzle} from "drizzle-orm/better-sqlite3";import Database from "better-sqlite3";import * as schema from "../db/schema";const sqlite = new Database("database.sqlite");const db = drizzle(sqlite, { schema });const auth = betterAuth({ database: drizzleAdapter(db, { provider: "sqlite" }), emailAndPassword: { enabled: true, }, rateLimit: { enabled: true, timeWindow: 60, maxRequests: 10, }, plugins: [ apiKey({ enableSessionForAPIKeys: true, permissions: { defaultPermissions: { files: ["read"], users: ["read"], }, }, rateLimit: { enabled: true, timeWindow: 60, maxRequests: 100 } }) ] });export default auth;• 这个文件可以命名为auth-cli.ts,只是为了生成数据库schema件 • 和auth.ts内容基本保持一致 • 需要导出auth
db
• db通过drizzle的方式生成和访问, drizzle.config.ts内容如下:
import { defineConfig } from "drizzle-kit";export default defineConfig({ dialect: "sqlite", // Cloudflare D1 底层是 SQLite schema: "./src/db/schema.ts", // 指向你通过脚本一键生成的 schema 文件 out: "./src/db/drizzle", // 未来通过 drizzle-kit 生成的SQL代码的存放目录 });生成schema
npx @better-auth/cli generate --config ./src/lib/auth-cli.ts --output ./src/db/schema.ts --yes2026-07-16T07:23:05.003Z WARN **[Better Auth]:** [better-auth] Base URL is not set. Set the baseURL option or BETTER_AUTH_URL env, or use a dynamic baseURL with allowedHosts for multi-host setups. Without it the origin is derived from the incoming request, and callbacks and redirects may not work correctly.🚀 Schema was overwritten successfully!• 注意:执行这条指令会生成 database.sqlite,删除即可• 生成的schema内容如下
CREATE TABLE `account` ( `id` text PRIMARY KEY NOT NULL, `account_id` text NOT NULL, `provider_id` text NOT NULL, `user_id` text NOT NULL, `access_token` text, `refresh_token` text, `id_token` text, `access_token_expires_at` integer, `refresh_token_expires_at` integer, `scope` text, `password` text, `created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL, `updated_at` integer NOT NULL, FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade ); --> statement-breakpoint CREATE INDEX `account_userId_idx` ON `account` (`user_id`);--> statement-breakpoint CREATE TABLE `apikey` ( `id` text PRIMARY KEY NOT NULL, `config_id` text DEFAULT 'default' NOT NULL, `name` text, `start` text, `reference_id` text NOT NULL, `prefix` text, `key` text NOT NULL, `refill_interval` integer, `refill_amount` integer, `last_refill_at` integer, `enabled` integer DEFAULT true, `rate_limit_enabled` integer DEFAULT true, `rate_limit_time_window` integer DEFAULT 60, `rate_limit_max` integer DEFAULT 100, `request_count` integer DEFAULT 0, `remaining` integer, `last_request` integer, `expires_at` integer, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `permissions` text, `metadata` text ); --> statement-breakpoint CREATE INDEX `apikey_configId_idx` ON `apikey` (`config_id`);--> statement-breakpoint CREATE INDEX `apikey_referenceId_idx` ON `apikey` (`reference_id`);--> statement-breakpoint CREATE INDEX `apikey_key_idx` ON `apikey` (`key`);--> statement-breakpoint CREATE TABLE `session` ( `id` text PRIMARY KEY NOT NULL, `expires_at` integer NOT NULL, `token` text NOT NULL, `created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL, `updated_at` integer NOT NULL, `ip_address` text, `user_agent` text, `user_id` text NOT NULL, FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade ); --> statement-breakpoint CREATE UNIQUE INDEX `session_token_unique` ON `session` (`token`);--> statement-breakpoint CREATE INDEX `session_userId_idx` ON `session` (`user_id`);--> statement-breakpoint CREATE TABLE `user` ( `id` text PRIMARY KEY NOT NULL, `name` text NOT NULL, `email` text NOT NULL, `email_verified` integer DEFAULT false NOT NULL, `image` text, `created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL, `updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL ); --> statement-breakpoint CREATE UNIQUE INDEX `user_email_unique` ON `user` (`email`);--> statement-breakpoint CREATE TABLE `verification` ( `id` text PRIMARY KEY NOT NULL, `identifier` text NOT NULL, `value` text NOT NULL, `expires_at` integer NOT NULL, `created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL, `updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL ); --> statement-breakpoint CREATE INDEX `verification_identifier_idx` ON `verification` (`identifier`);生成sql
npx drizzle-kit generateNo config path provided, using default 'drizzle.config.ts'Reading config file '/Users/lixiaobo/cloudflare/better-auth-cloudflare-infra-demo/drizzle.config.ts'**5 tables****account** 13 columns 1 indexes 1 fks**apikey** 22 columns 3 indexes 0 fks**session** 8 columns 2 indexes 1 fks**user** 7 columns 1 indexes 0 fks**verification** 6 columns 1 indexes 0 fks[✓] Your SQL migration file ➜ **src/db/drizzle/0000_rainy_tony_stark.sql** 🚀createDb
import { drizzle } from "drizzle-orm/d1";import * as schema from "./schema";/** * 创建 Drizzle D1 实例 * 在每次请求时调用,绑定 Cloudflare D1 数据库 */export const createDb = (d1: Cloudflare.Env["better_auth_cloudflare_demo"]) => { return drizzle(d1, { schema }); };// 推导 Drizzle 实例类型 export type Database = ReturnType<typeof createDb>;middleware
cors,为了测试
import { cors } from "hono/cors";import {isTrustedOrigin} from "../constants";export const corsMiddleware = cors({ origin: (origin) => { if(!origin){ return null; } return isTrustedOrigin(origin) ? origin: null; }, allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], maxAge: 600, credentials: true, // 必须设为 true,支持 Better Auth 跨域写入 Cookie });apikey
import { createMiddleware } from "hono/factory";import {AuthVariables} from '../types';import {ApiKeyInfo, createAuth, SessionUser} from "../lib/auth";import {PUBLIC_PATH_PREFIXES} from "../constants";import { eq } from "drizzle-orm";import {user} from "../db/schema";import {createDb} from "../db/db";export const apiKeyMiddleware = createMiddleware<AuthVariables>(async (c, next) => { // 初始化上下文状态 c.set("user", null); c.set("apiKey", null); c.set("authType", null); const isPublic = PUBLIC_PATH_PREFIXES.some(prefix=> c.req.path.startsWith(prefix)); //白名单:如果请求是去往注册、登录或其他认证接口,直接放行,不检查 API Key if (isPublic) { return await next(); } // 单次请求内只创建一次 db,供 createAuth 与用户查询复用 const db = createDb(c.env.better_auth_cloudflare_demo); const auth = createAuth(c.env, db); const reqHeaders = c.req.raw.headers; const authHeader = c.req.header("Authorization"); try { // 1. 优先校验:API Key (S2S 场景) // RFC 6750: Bearer scheme 大小写不敏感,这里做大小写不敏感判断 if (authHeader) { const match = /^Bearer\s+(.+)$/i.exec(authHeader); if (match) { const plainApiKey = match[1].trim(); const verifyResult = await auth.api.verifyApiKey({ body: { key: plainApiKey } }); // Better Auth 校验通过会返回 { valid: true, key: ApiKeyObject } if (!verifyResult || !verifyResult.key) { // 提供了 Bearer 但校验失败:直接 401,不再降级到 Session, // 避免掩盖密钥配置错误或造成鉴权语义混淆 return c.json({ success: false, message: "未授权:无效或过期的 API Key" }, 401); } c.set("apiKey", verifyResult.key as ApiKeyInfo); c.set("authType", "S2S"); const userId = verifyResult.key.referenceId; const dbUser = await db .select({ id: user.id, name: user.name, email: user.email, emailVerified: user.emailVerified, image: user.image, createdAt: user.createdAt, updatedAt: user.updatedAt, }) .from(user) .where(eq(user.id, userId)) .get(); if (!dbUser) { // API Key 有效但对应用户已删除/数据不一致:视为异常,直接拦截 console.error(`API Key 校验通过但用户不存在: key.referenceId=${userId}`); return c.json({ success: false, message: "未授权:API Key 关联的用户不存在" }, 401); } c.set("user", dbUser as SessionUser); return await next(); } // 提供了 Authorization 头但不是 Bearer 形式,按无凭据处理 } // 2. 降级校验:Session Cookie (SPA 场景) const session = await auth.api.getSession({ headers: reqHeaders }); if (session && session.user) { c.set("user", session.user as SessionUser); c.set("authType", "SPA"); return await next(); // 鉴权成功,放行 } // 3. 两者皆无,直接拦截 return c.json({ success: false, message: "未授权:请登录或提供有效的 API Key" }, 401); } catch (error) { console.error("认证中间件异常:", error); return c.json({ success: false, message: "服务器内部错误" }, 500); } });index
export {apiKeyMiddleware} from './apiKey'export {corsMiddleware} from './cors'routes
auth
import { Hono } from 'hono'import { createAuth} from "../lib/auth";import { type AuthVariables } from "../types"; // 引入你已经写好的那个完整声明 export const authRouter = new Hono<AuthVariables>();export const AuthRouterPrefix = "/auth";// 1. 核心路由托管与注册拦截 authRouter.all("/*", async (c, next) => { const auth = createAuth(c.env); const response = await auth.handler(c.req.raw); const isSignUp = (c.req.method === "POST") && (c.req.path.startsWith("/api/auth/sign-up/")); if (!response.ok) return response; if (isSignUp && response.ok) { try { const data = await response.clone().json() as any; // ... 你的前置拦截和 apiKey 申请逻辑保持不变 ... if (data?.user?.id) { const apiKeyResult = await auth.api.createApiKey({ body: { name: `${data.user.name || 'User'} 的初始密钥`, userId: data.user.id } }); const newBodyString = JSON.stringify({ ...data, apiKey: { id: apiKeyResult.id, name: apiKeyResult.name, secretKey: apiKeyResult.key, expiresAt: apiKeyResult.expiresAt } }); // 克隆原有的 headers 实例 const newHeaders = new Headers(response.headers); // 删掉旧的、错误的长度标记! // 这样边缘运行时就会根据 newBodyString 的实际长度,在发送前帮我们自动重写该字段。 newHeaders.delete("content-length"); return new Response(newBodyString, { status: response.status, headers: newHeaders // 👈 投递修正后、安全的全新 Headers 容器 }); } } catch (e) { console.error(e); } } return response; });user
import { Hono } from 'hono'import { type AuthVariables } from "../types";import {createAuth} from "../lib/auth";export const userRouter = new Hono<AuthVariables>();export const UserRouterPrefix = "/user";// GET /profile 接口 userRouter.get("/profile", async (c) => { const user = c.get("user"); if (!user) { return c.json({ error: "unauthorized" }, 401); } return c.json(user); }); userRouter.put("/updateExpire", async (c) => { const currentUser = c.get("user"); if (!currentUser) { return c.json({ success: false, error: "unauthorized" }, 401); } const auth = createAuth(c.env); try { const reqBody = await c.req.json().catch(() => null) as | { keyId?: unknown; userId?: unknown; expiresIn?: unknown } | null; if (!reqBody) { return c.json({ success: false, error: "Invalid request body" }, 400); } const { keyId, userId, expiresIn } = reqBody; if (typeof keyId !== "string" || keyId.length === 0) { return c.json({ success: false, error: "Missing required fields: keyId" }, 400); } // 仅允许属主操作自己的 API Key,防止 IDOR const ownerUserId = typeof userId === "string" && userId.length > 0 ? userId : currentUser.id; if (ownerUserId !== currentUser.id) { return c.json({ success: false, error: "forbidden: can only operate on your own API Key" }, 403); } // expiresIn 为可选,默认 7 天;必须是有限正整数 const rawSeconds = expiresIn !== undefined ? Number(expiresIn) : 60 * 60 * 24 * 7; if (!Number.isFinite(rawSeconds) || rawSeconds <= 0) { return c.json({ success: false, error: "expiresIn must be a positive finite number (seconds)" }, 400); } const seconds = Math.floor(rawSeconds); const apiKeyResult = await auth.api.updateApiKey({ body: { keyId, userId: ownerUserId, expiresIn: seconds, enabled: true } }); return c.json({ success: true, apiKey: { id: apiKeyResult.id, name: apiKeyResult.name, expiresAt: apiKeyResult.expiresAt // 返回给前端最新的过期绝对时间戳/字符串 } }); } catch (e) { console.error("Update API Key error:", e); return c.json({ success: false, error: "Internal Server Error" }, 500); } });index
import { Hono } from 'hono'import { type AuthVariables } from "../types";import {authRouter, AuthRouterPrefix} from "./auth";import {userRouter, UserRouterPrefix} from "./user";export const apiRouter = new Hono<AuthVariables>();export const ApiRouterPrefix = "/api"; apiRouter.route(AuthRouterPrefix, authRouter); apiRouter.route(UserRouterPrefix, userRouter);src
constants
/** * 允许匿名访问(无需 API Key 或 Session)的公开路由前缀白名单 */export const PUBLIC_PATH_PREFIXES = [ "/api/auth/", // 认证相关(注册、登录、第三方回调等) "/api/public/", // 公开的开放数据接口 "/static/" // 静态资源文件夹 ] as const;export const TRUSTED_ORIGINS=[ "http://localhost:5173", "https://*.better-auth-cloudflare-ui-vue-demo.pages.dev"] as const;export function isTrustedOrigin (origin: string) { return TRUSTED_ORIGINS.some(pattern => { if (pattern.includes("*")) { const regex = new RegExp("^" + pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$"); return regex.test(origin); } return pattern === origin; } ) }types
import { type SessionUser, type ApiKeyInfo } from "./lib/auth";export type AuthVariables = { Variables: { user: SessionUser | null; apiKey: ApiKeyInfo | null; authType: "SPA" | "S2S" | null; }; Bindings: Cloudflare.Env; };index
import { Hono } from 'hono'import {apiKeyMiddleware, corsMiddleware} from "./middleware";import type {AuthVariables} from "./types";import {apiRouter, ApiRouterPrefix} from "./routes";const app = new Hono<AuthVariables>() app.use("*", corsMiddleware); app.use("*", apiKeyMiddleware); app.route(ApiRouterPrefix, apiRouter);if (process.env.NODE_ENV !== "production") { console.log("=== 当前可用的 API 端点列表 ==="); app.routes.forEach(r => console.log(`[${r.method}]`.padEnd(8) + r.path)); console.log("==============================="); }export default app部署和测试
生成数据库
• 生成本地数据库
wrangler d1 execute better_auth_cloudflare_demo --local --file=./src/db/drizzle/0000_rainy_tony_stark.sql迁移sql到线上
• 创建线上数据库
wrangler d1 create better-auth-cloudflare-demo▲ [WARNING] **Proxy environment variables detected. We'll use your proxy for fetch requests.** ⛅️ wrangler 4.106.0────────────────────✅ Successfully created DB 'better-auth-cloudflare-demo' in region APACCreated your new D1 database.To access your new D1 Database in your Worker, add the following snippet to your configuration file:{ "d1_databases": [ { "binding": "better_auth_cloudflare_demo", "database_name": "better-auth-cloudflare-demo", "database_id": "43240d79-660b-401b-9174-90d9e33a791f" } ]}✔ **Would you like Wrangler to add it on your behalf?** … yes✔ **What binding name would you like to use?** … better_auth_cloudflare_demo✔ **For local dev, do you want to connect to the remote resource instead of a local resource?** … yes• 创建表
wrangler d1 execute better_auth_cloudflare_demo --remote --file=/Users/lixiaobo/cloudflare/better-auth-cloudflare-infra-demo/src/db/drizzle/0000_gifted_raza.sql▲ [WARNING] **Proxy environment variables detected. We'll use your proxy for fetch requests.** ⛅️ wrangler 4.106.0 (update available 4.111.0)───────────────────────────────────────────────**Resource location:** remote ✔ **⚠️ This process may take some time, during which your D1 database will be unavailable to serve queries.** **Ok to proceed?** … yes🌀 Executing on remote database better_auth_cloudflare_demo (43240d79-660b-401b-9174-90d9e33a791f):🌀 To execute on your local development database, remove the --remote flag from your wrangler command.Note: if the execution fails to complete, your DB will return to its original state and you can safely retry.├ 🌀 Uploading 43240d79-660b-401b-9174-90d9e33a791f.1c9a1c2321650333.sql│ 🌀 Uploading complete.│🌀 Starting import...🌀 Processed 13 queries.🚣 Executed 13 queries in 6.15ms (21 rows read, 23 rows written) Database is currently at bookmark 00000001-00000006-000050aa-3970c87809b19d5438d9291a4ef87ca5.┌────────────────────────┬───────────┬──────────────┬────────────────────┐│ Total queries executed │ Rows read │ Rows written │ Database size (MB) │├────────────────────────┼───────────┼──────────────┼────────────────────┤│ 13 │ 21 │ 23 │ 0.09 │└────────────────────────┴───────────┴──────────────┴────────────────────┘部署worker
wrangler deploy▲ [WARNING] **Proxy environment variables detected. We'll use your proxy for fetch requests.** ⛅️ wrangler 4.106.0 (update available 4.111.0)───────────────────────────────────────────────Cloudflare collects anonymous telemetry about your usage of Wrangler. Learn more at https://github.com/cloudflare/workers-sdk/tree/main/packages/wrangler/telemetry.mdTotal Upload: 3274.38 KiB / gzip: 541.30 KiBWorker Startup Time: 76 msYour Worker has access to the following bindings:Binding Resource env.better_auth_cloudflare_demo (better-auth-cloudflare-demo) D1 Database env.BETTER_AUTH_URL ("https://better-auth-cloudflare-demo.s...") Environment Variable Uploaded better-auth-cloudflare-demo (16.28 sec)Deployed better-auth-cloudflare-demo triggers (2.86 sec) https://better-auth-cloudflare-demo.shakingwaves.workers.devCurrent Version ID: 3edce064-f6ad-408c-b2c2-1e9e4f176052测试
• 注册
curl -X POST https://better-auth-cloudflare-demo.shakingwaves.workers.dev/api/auth/sign-up/email -H 'Content-Type: application/json' -d '{"email":"test@example.com","password":"12345678","name":"test"}'{"token":"UxwoNKaRUVCTslXyzm1Kh5ojhQubaOwx","user":{"name":"test","email":"test@example.com","emailVerified":false,"image":null,"createdAt":"2026-07-16T11:02:47.317Z","updatedAt":"2026-07-16T11:02:47.317Z","id":"SKBS4MauWv1CsVXdg9eC6AqM5yDnUXzg"},"apiKey":{"id":"R3tKfvaHKVNOqEf8mLl8lD7lybdqryg0","name":"test 的初始密钥","secretKey":"wsVgNDpIpjQIUtYJmJgCtnvZHatAQmdqQwdQQjpImqMuphMHhSwqqFwSkXrcdOos","expiresAt":null}}• 访问,不携带secretKey
curl -X GET https://better-auth-cloudflare-demo.shakingwaves.workers.dev/api/user/profile{"success":false,"message":"未授权:请登录或提供有效的 API Key"}• 访问,携带secretKey
curl -X GET https://better-auth-cloudflare-demo.shakingwaves.workers.dev/api/user/profile -H "Authorization: Bearer wsVgNDpIpjQIUtYJmJgCtnvZHatAQmdqQwdQQjpImqMuphMHhSwqqFwSkXrcdOos"{"id":"SKBS4MauWv1CsVXdg9eC6AqM5yDnUXzg","name":"test","email":"test@example.com","emailVerified":false,"image":null,"createdAt":"2026-07-16T11:02:47.317Z","updatedAt":"2026-07-16T11:02:47.317Z"}例子
截止日期
curl -X POST https://better-auth-cloudflare-demo.shakingwaves.workers.dev/api/auth/sign-up/email -H 'Content-Type: application/json' -d '{"email":"test2@example.com","password":"12345678","name":"test"}'{"token":"eqX199U7Ppjlw2dHQY0VQZ2XR2uobTiS","user":{"name":"test","email":"test2@example.com","emailVerified":false,"image":null,"createdAt":"2026-07-17T02:23:52.512Z","updatedAt":"2026-07-17T02:23:52.512Z","id":"pinIEjvmZJ9arISFI8aoQcDAWaZtZskk"},"apiKey":{"id":"0w5HUyfHMEbf6FuNlREsrLgI1U0UM5xw","name":"test 的初始密钥","secretKey":"rCoYYJHzMPCBBmoDvEOkiodTYspzSqBputRbgzJDUSoMtcyokIdVngKgaYsBgDPy","expiresAt":null}}**%** curl -X PUT https://better-auth-cloudflare-demo.shakingwaves.workers.dev/api/user/updateExpire \-H "Authorization: Bearer rCoYYJHzMPCBBmoDvEOkiodTYspzSqBputRbgzJDUSoMtcyokIdVngKgaYsBgDPy" \-H "Content-Type: application/json" \-d '{ "keyId": "0w5HUyfHMEbf6FuNlREsrLgI1U0UM5xw", "userId": "pinIEjvmZJ9arISFI8aoQcDAWaZtZskk", "expiresIn": 2592000}'{"success":true,"apiKey":{"id":"0w5HUyfHMEbf6FuNlREsrLgI1U0UM5xw","name":"test 的初始密钥","expiresAt":"2026-08-16T02:24:30.732Z"}}类似调用次数和同时在线设备数等都可以实现
夜雨聆风