Better Auth 是一个全面的、开源的 TypeScript 认证框架。它设计为框架无关,但与 Next.js 集成良好,并提供了大量开箱即用的功能。

1. 替换 auth 包的依赖项

auth 包中卸载现有的 Clerk 依赖…

Terminal
pnpm remove @clerk/nextjs @clerk/themes @clerk/types --filter @repo/auth

然后安装 Better Auth 依赖项:

Terminal
pnpm add better-auth next --filter @repo/auth

此外,在 auth 软件包依赖关系中添加 @repo/database

2. 更新环境变量

使用以下命令生成密钥,并将其添加到每个 Next.js 应用(appwebapi)的 .env.local 文件中:

Terminal
npx @better-auth/cli secret

这将在 .env.local 文件中添加一个 BETTER_AUTH_SECRET 环境变量。

3. 设置服务端和客户端认证

用以下代码更新 auth 包的文件:

import { betterAuth } from 'better-auth';
import { nextCookies } from "better-auth/next-js";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { database } from "@repo/database"

export const auth = betterAuth({
  database: prismaAdapter(database, {
    provider: 'postgresql',
  }),
  plugins: [
    nextCookies()
    // organization() // if you want to use organization plugin
  ],
  //...add more options here
});

更多信息请参阅 Better Auth 安装指南

4. 更新认证组件

更新 auth 包中的 sign-in.tsxsign-up.tsx 组件,使用 client 文件中的 signInsignUp 函数。

"use client";

import { signIn } from '../client';
import { useState } from 'react';

export const SignIn = () => {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  return (
    <form
      onSubmit={async (e) => {
        e.preventDefault();
        await signIn.email({
          email,
          password,
        })
      }}
    >
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <button type="submit">Sign in</button>
    </form>
  );
}

您可以使用不同的登录方式,如社交账号、手机号、用户名等。更多关于 Better Auth 基本用法的信息。

5. 生成 Prisma 模型

从根目录运行以下命令为 Better Auth 生成 Prisma 模型:

Terminal
npx @better-auth/cli generate --output ./packages/database/prisma/schema.prisma --config ./packages/auth/server.ts

You may have to comment out the server-only directive in packages/database/index.ts temporarily. Ensure you have environment variables set.

6. 更新 Provider 文件

Better Auth 没有作为高阶组件的 Provider 概念,因此您可以完全删除它,或者像这样替换为一个存根:

packages/auth/provider.tsx
import type { ReactNode } from 'react';

type AuthProviderProps = {
  children: ReactNode;
};

export const AuthProvider = ({ children }: AuthProviderProps) => children;

7. 更改中间件

auth 包中的中间件更改为以下内容:

packages/auth/middleware.ts
import type { NextRequest } from "next/server";
import { NextResponse } from 'next/server';

const isProtectedRoute = (request: NextRequest) => {
  return request.url.startsWith("/dashboard"); // change this to your protected route
}

export const authMiddleware = async (request: NextRequest) => {
  const url = new URL('/api/auth/get-session', request.nextUrl.origin);
  const response = await fetch(url, {
    headers: {
      cookie: request.headers.get('cookie') || '',
    },
  });

  const session = await response.json();
  
  if (isProtectedRoute(request) && !session) {
    return NextResponse.redirect(new URL("/sign-in", request.url));
  }
  
  return NextResponse.next();
}

8. 更新您的应用

从这里开始,您需要用 Better Auth 替换应用中剩余的 Clerk 实现。

Here is some inspiration:

const user = await currentUser();
const { redirectToSignIn } = await auth();

// to

const session = await auth.api.getSession({
  headers: await headers(), // from next/headers
});
if (!session?.user) {
  return redirect('/sign-in'); // from next/navigation
}
// do something with session.user
const { orgId } = await auth();

// to

const h = await headers(); // from next/headers
const session = await auth.api.getSession({
  headers: h,
});
const orgId = session?.session.activeOrganizationId;
const fullOrganization = await auth.api.getFullOrganization({
  headers: h,
  query: { organizationId: orgId },
});
webhooks/stripe/route.ts
import { clerkClient } from '@repo/auth/server';

const clerk = await clerkClient();
const users = await clerk.users.getUserList();
const user = users.data.find(
  (user) => user.privateMetadata.stripeCustomerId === customerId
);

// to

import { database } from '@repo/database';

const user = await database.user.findFirst({
  where: {
    privateMetadata: {
      contains: { stripeCustomerId: customerId },
    },
  },
});

关于组织功能的使用,请查看组织插件和更多Better Auth 文档