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 文件中。 你的框架开发服务器会读取它。

.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 配置为认证提供方。

convex/auth.config.ts
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 目录是一个本地安装的组件。

convex/betterAuth/convex.config.ts
import { defineComponent } from "convex/server";

const component = defineComponent("betterAuth");

export default component;

注册组件

在你的 Convex 项目中注册 Better Auth 组件。

convex/convex.config.ts
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 错误。
convex/betterAuth/auth.ts
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 组件导出适配器函数。

convex/betterAuth/adapter.ts
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 服务器交互。

lib/auth-client.ts
import { convexClient } from "@convex-dev/better-auth/client/plugins";
import { createAuthClient } from "better-auth/react";

export const authClient = createAuthClient({
  plugins: [convexClient()],
});

配置 Next.js 服务端辅助函数

配置一组用于已认证 SSR、服务端函数和路由处理程序的辅助函数。

lib/auth-server.ts
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 路由处理程序。

convex/http.ts
import { httpRouter } from "convex/server";
import { authComponent, createAuth } from "./betterAuth/auth";

const http = httpRouter();

authComponent.registerRoutes(http, createAuth);

export default http;

设置路由处理程序,将来自 Next.js 的认证请求代理到你的 Convex 部署。

app/api/auth/[...all]/route.ts
import { handler } from "@/lib/auth-server";

export const { GET, POST } = handler;

设置 Convex 客户端提供方

创建一个 ConvexClientProvider 组件。

components/ConvexClientProvider.tsx
"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 组件包装你的应用。

app/layout.tsx
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 文件中创建与认证相关的函数,如下:

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。

app/sign-in/page.tsx
"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 钩子查询或变更数据,就像平常一样。

app/dashboard/page.tsx
"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;

使用服务器辅助函数

您可以在服务器端使用辅助函数进行身份认证相关操作。

app/protected/page.tsx
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 时使用,而不是用于保护资源。

在服务器组件中预加载:

app/landing/page.tsx
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;

在客户端组件中渲染预加载的数据:

app/landing/header.tsx
"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;

额外资源