Convex 集成
将 Better Auth 与 Convex 集成。
在本指南中,我们将介绍将 Better Auth 与 Convex 集成的步骤。
先决条件
创建一个 Convex 项目
要将 Better Auth 与 Convex 一起使用,你需要一个 Convex 项目。如果你还没有,请运行以下命令创建一个新项目。更多详情请参阅 Convex 文档。
npm create convex@latest
# 选择用户认证:
# > none运行 convex dev
在设置过程中运行 CLI 将初始化你的 Convex 部署 如果它还不存在,并在此过程中保持生成的类型为最新状态。请保持它运行。
npx convex dev安装包
安装 Better Auth 和 Better Auth 的 Convex 组件,并确保你使用的是最新版本的 Convex。
npm install better-auth @convex-dev/better-auth@convex-dev/better-auth 由 Convex 维护。
设置环境变量
生成用于加密和生成哈希的密钥。如果你已安装 openssl,请使用下面的命令,或者使用 npx auth secret 生成一个。
npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)将你的网站 URL 添加到你的 Convex 部署中。
npx convex env set SITE_URL http://localhost:3000将环境变量添加到 npx convex dev 创建的 .env.local 文件中。
你的框架开发服务器会读取它。
# `npx convex dev` 使用的部署
CONVEX_DEPLOYMENT=dev:adjective-animal-123 # 团队:team-name,项目:project-name
NEXT_PUBLIC_CONVEX_URL=https://adjective-animal-123.convex.cloud
# 与 NEXT_PUBLIC_CONVEX_URL 相同,但以 .site 结尾
NEXT_PUBLIC_CONVEX_SITE_URL=https://adjective-animal-123.convex.site
# 你本地的网站 URL
NEXT_PUBLIC_SITE_URL=http://localhost:3000由于 Better Auth 实例运行在 Convex 上,auth 实例使用的环境变量应通过 Convex CLI 或控制台进行配置,而不是放在 .env.local 中。
例如:BETTER_AUTH_SECRET、GITHUB_CLIENT_ID、GITHUB_CLIENT_SECRET ...
添加 Convex 认证配置
添加一个 convex/auth.config.ts 文件,以将 Better Auth 配置为认证提供方。
import { getAuthConfigProvider } from "@convex-dev/better-auth/auth-config";
import type { AuthConfig } from "convex/server";
export default {
providers: [getAuthConfigProvider()],
} satisfies AuthConfig;创建 Better Auth Convex 组件
Convex 组件可以从 NPM 或本地文件夹安装。虽然 NPM 版本 可用,但本指南使用本地文件夹设置,以充分发挥 Better Auth 的全部潜力。
创建组件定义
创建一个 convex/betterAuth/convex.config.ts 文件来定义该组件。这将向 Convex 表明 convex/betterAuth 目录是一个本地安装的组件。
import { defineComponent } from "convex/server";
const component = defineComponent("betterAuth");
export default component;注册组件
在你的 Convex 项目中注册 Better Auth 组件。
import { defineApp } from "convex/server";
import betterAuth from "./betterAuth/convex.config";
const app = defineApp();
app.use(betterAuth);
export default app;创建 Better Auth 实例
创建一个 Better Auth 实例并初始化该组件。
- 在下一步之前,此文件尚未完成。
- 在你保存该文件之前,会显示一些 TypeScript 错误。
import { createClient } from "@convex-dev/better-auth";
import { convex } from "@convex-dev/better-auth/plugins";
import type { GenericCtx } from "@convex-dev/better-auth/utils";
import type { BetterAuthOptions } from "better-auth";
import { betterAuth } from "better-auth";
import { components } from "../_generated/api";
import type { DataModel } from "../_generated/dataModel";
import authConfig from "../auth.config";
import schema from "./schema";
// Better Auth 组件
export const authComponent = createClient<DataModel, typeof schema>(
components.betterAuth,
{
local: { schema },
verbose: false,
},
);
// Better Auth 选项
export const createAuthOptions = (ctx: GenericCtx<DataModel>) => {
return {
appName: "My App",
baseURL: process.env.SITE_URL,
secret: process.env.BETTER_AUTH_SECRET,
database: authComponent.adapter(ctx),
emailAndPassword: {
enabled: true,
},
plugins: [convex({ authConfig })],
} satisfies BetterAuthOptions;
};
// 供 `auth` CLI 使用
export const options = createAuthOptions({} as GenericCtx<DataModel>);
// Better Auth 实例
export const createAuth = (ctx: GenericCtx<DataModel>) => {
return betterAuth(createAuthOptions(ctx));
};生成 schema
配置好你的 Better Auth 实例后,运行下面的命令来生成 convex/betterAuth/schema.ts。
npx auth generate --config ./convex/betterAuth/auth.ts --output ./convex/betterAuth/schema.ts如果你需要修改 Better Auth 实例,请使用此命令更新 schema。
导出适配器函数
为 Better Auth 组件导出适配器函数。
import { createApi } from "@convex-dev/better-auth";
import { createAuthOptions } from "./auth";
import schema from "./schema";
export const {
create,
findOne,
findMany,
updateOne,
updateMany,
deleteOne,
deleteMany,
} = createApi(schema, createAuthOptions);创建 Better Auth 客户端实例
创建 Better Auth 客户端实例,用于从你的客户端与 Better Auth 服务器交互。
import { convexClient } from "@convex-dev/better-auth/client/plugins";
import { createAuthClient } from "better-auth/react";
export const authClient = createAuthClient({
plugins: [convexClient()],
});配置 Next.js 服务端辅助函数
配置一组用于已认证 SSR、服务端函数和路由处理程序的辅助函数。
import { convexBetterAuthNextJs } from "@convex-dev/better-auth/nextjs";
export const {
handler,
preloadAuthQuery,
isAuthenticated,
getToken,
fetchAuthQuery,
fetchAuthMutation,
fetchAuthAction,
} = convexBetterAuthNextJs({
convexUrl: process.env.NEXT_PUBLIC_CONVEX_URL!,
convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL!,
});挂载处理程序
在你的 Convex 部署上注册 Better Auth 路由处理程序。
import { httpRouter } from "convex/server";
import { authComponent, createAuth } from "./betterAuth/auth";
const http = httpRouter();
authComponent.registerRoutes(http, createAuth);
export default http;设置路由处理程序,将来自 Next.js 的认证请求代理到你的 Convex 部署。
import { handler } from "@/lib/auth-server";
export const { GET, POST } = handler;设置 Convex 客户端提供方
创建一个 ConvexClientProvider 组件。
"use client";
import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react";
import { ConvexReactClient } from "convex/react";
import { authClient } from "@/lib/auth-client";
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
export function ConvexClientProvider({
children,
initialToken,
}: {
children: React.ReactNode;
initialToken?: string | null;
}) {
return (
<ConvexBetterAuthProvider
client={convex}
authClient={authClient}
initialToken={initialToken}
>
{children}
</ConvexBetterAuthProvider>
);
}使用 ConvexClientProvider 组件包装你的应用。
import { ConvexClientProvider } from "@/components/ConvexClientProvider";
import { getToken } from "@/lib/auth-server";
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const token = await getToken();
return (
<html>
<body>
<ConvexClientProvider initialToken={token}>
{children}
</ConvexClientProvider>
</body>
</html>
);
}完成 🎉
您现已准备好开始使用 Better Auth 结合 Convex。
使用方法
创建函数
Better Auth 的 auth.api 方法通常运行在服务器端,但当 Convex 作为您的后端时,这些方法需要在 Convex 函数中运行。然后,客户端可通过像 useMutation 这样的钩子调用这些函数,也可以在服务器函数或其他服务器代码中使用诸如 fetchAuthMutation 之类的 auth-server 工具调用。
Convex 习惯将函数放在 convex/ 目录。例如,您可以在 convex/auth.ts 文件中创建与认证相关的函数,如下:
import { query } from "./_generated/server";
export const getCurrentUser = query({
args: {},
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
return identity;
},
});
// ... 其他函数使用 Better Auth 客户端
您可以像通常使用 Better Auth 客户端一样使用 authClient。
"use client";
import { Button } from "@/components/ui/button";
import { authClient } from "@/lib/auth-client";
const Page = () => {
return (
<div className="flex flex-col items-center justify-center h-screen">
<Button
onClick={async () => {
await authClient.signIn.social({
provider: "github",
callbackURL: "/dashboard",
});
}}
>
通过 GitHub 登录
</Button>
</div>
);
};
export default Page;使用 Convex React 钩子
如果您创建了 Convex 函数,可以在客户端使用 Convex React 钩子查询或变更数据,就像平常一样。
"use client";
import { useQuery } from "convex/react";
import { api } from "@/convex/_generated/api";
const Page = () => {
const user = useQuery(api.auth.getCurrentUser);
if (user === undefined) {
return <div>加载中...</div>;
}
if (user === null) {
return <div>未授权</div>;
}
return (
<div>
<pre>{JSON.stringify(user, null, 2)}</pre>
</div>
);
};
export default Page;使用服务器辅助函数
您可以在服务器端使用辅助函数进行身份认证相关操作。
import { isAuthenticated } from "@/lib/auth-server";
const Page = async () => {
const hasToken = await isAuthenticated();
if (!hasToken) {
return <div>未授权</div>;
}
return (
<div>
<p>您好</p>
</div>
);
};
export default Page;使用 SSR 服务器组件
Convex 查询可以在服务器组件中预加载,并通过 preloadAuthQuery 和 usePreloadedAuthQuery 在客户端组件中渲染。
preloadAuthQuery 建议在您希望根据数据状态处理不同 UI 时使用,而不是用于保护资源。
在服务器组件中预加载:
import { api } from "@/convex/_generated/api";
import { preloadAuthQuery } from "@/lib/auth-server";
import Header from "./header";
const Page = async () => {
const preloadedUserQuery = await preloadAuthQuery(api.auth.getCurrentUser);
return (
<div>
<Header preloadedUserQuery={preloadedUserQuery} />
</div>
);
};
export default Page;在客户端组件中渲染预加载的数据:
"use client";
import { usePreloadedAuthQuery } from "@convex-dev/better-auth/nextjs/client";
import type { Preloaded } from "convex/react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import type { api } from "@/convex/_generated/api";
export const Header = ({
preloadedUserQuery,
}: {
preloadedUserQuery: Preloaded<typeof api.auth.getCurrentUser>;
}) => {
const user = usePreloadedAuthQuery(preloadedUserQuery);
return (
<div>
{user ? (
<Button asChild>
<Link href="/dashboard">仪表盘</Link>
</Button>
) : (
<Button asChild>
<Link href="/sign-in">登录</Link>
</Button>
)}
</div>
);
};
export default Header;