# 从 Auth.js 迁移到 Better Auth (/docs/guides/next-auth-migration-guide)

从 Auth.js 迁移到 Better Auth 的分步指南。



## 介绍 [#介绍]

在本指南中，我们将逐步演示如何将项目从 [Auth.js](https://authjs.dev/)（前身为 [NextAuth.js](https://next-auth.js.org/)）迁移到 Better Auth。由于这两个项目的设计理念不同，迁移需要仔细规划和实施。如果您当前的设置运行良好，则无需紧急迁移。我们仍然会为 Auth.js 处理安全补丁和关键问题。

但如果您正在启动新项目或在当前设置中遇到挑战，我们强烈建议使用 Better Auth。我们的路线图包括以前仅 Auth.js 才有的功能，我们希望这能加强生态系统的统一，而不会导致分裂。

<Steps>
  <Step>
    ## 创建 Better Auth 实例 [#创建-better-auth-实例]

    在开始迁移过程之前，在您的项目中设置 Better Auth。请按照[安装指南](/docs/installation)开始。

    例如，如果您使用 GitHub OAuth 提供程序，以下是 `auth.ts` 文件的对比。

    <Tabs items="[&#x22;Auth.js&#x22;, &#x22;Better Auth&#x22;]">
      <Tab value="Auth.js">
        ```ts
        import NextAuth from "next-auth"
        import GitHub from "next-auth/providers/github"
          
        export const { handlers, signIn, signOut, auth } = NextAuth({
          providers: [GitHub],
        })
        ```
      </Tab>

      <Tab value="Better Auth">
        ```ts
        import { betterAuth } from "better-auth";

        export const auth = betterAuth({
          socialProviders: { 
            github: { 
              clientId: process.env.GITHUB_CLIENT_ID!, 
              clientSecret: process.env.GITHUB_CLIENT_SECRET!, 
            }, 
          }, 
        })
        ```
      </Tab>
    </Tabs>

    <Callout type="info">
      现在 Better Auth 支持无需任何数据库的无状态会话管理。如果您之前在 Auth.js 中使用了数据库适配器，可以参考下面的[数据库模型](#数据库模型)以了解与 Better Auth 核心架构的差异。
    </Callout>
  </Step>

  <Step>
    ## 创建客户端实例 [#创建客户端实例]

    此客户端实例包含一组用于与 Better Auth 服务端实例交互的函数。有关更多信息，请参阅[客户端文档](/docs/concepts/client)。

    ```ts title="auth-client.ts"
    import { createAuthClient } from "better-auth/react"

    export const authClient = createAuthClient()
    ```
  </Step>

  <Step>
    ## 更新路由处理程序 [#更新路由处理程序]

    将 `/app/api/auth/[...nextauth]` 文件夹重命名为 `/app/api/auth/[...all]`，以避免混淆。然后，按如下方式更新 `route.ts` 文件：

    <Tabs items="[&#x22;Auth.js&#x22;, &#x22;Better Auth&#x22;]">
      <Tab value="Auth.js">
        ```ts
        import { handlers } from "@/lib/auth"

        export const { GET, POST } = handlers
        ```
      </Tab>

      <Tab value="Better Auth">
        ```ts
        import { auth } from "@/lib/auth";
        import { toNextJsHandler } from "better-auth/next-js";

        export const { POST, GET } = toNextJsHandler(auth)
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step>
    ## 会话管理 [#会话管理]

    在本节中，我们将了解 Better Auth 与 Auth.js 相比如何管理会话。有关更多信息，请参阅[会话管理文档](/docs/concepts/session-management)。

    ### 客户端 [#客户端]

    #### 登录 [#登录]

    以下是在登录用户时，Auth.js 与 Better Auth 的区别。例如，使用 GitHub OAuth 提供程序：

    <Tabs items="[&#x22;Auth.js&#x22;, &#x22;Better Auth&#x22;]">
      <Tab value="Auth.js">
        ```ts
        "use client"

        import { signIn } from "next-auth/react"

        signIn("github")
        ```
      </Tab>

      <Tab value="Better Auth">
        ```ts
        "use client"

        import { authClient } from "@/lib/auth-client";

        const { data, error } = await authClient.signIn.social({
          provider: "github",
        })
        ```
      </Tab>
    </Tabs>

    #### 登出 [#登出]

    以下是在登出用户时，Auth.js 与 Better Auth 的区别。

    <Tabs items="[&#x22;Auth.js&#x22;, &#x22;Better Auth&#x22;]">
      <Tab value="Auth.js">
        ```ts
        "use client"

        import { signOut } from "next-auth/react"

        signOut()
        ```
      </Tab>

      <Tab value="Better Auth">
        ```ts
        "use client"

        import { authClient } from "@/lib/auth-client";

        const { data, error } = await authClient.signOut()
        ```
      </Tab>
    </Tabs>

    #### 获取会话 [#获取会话]

    以下是在获取当前活跃会话时，Auth.js 与 Better Auth 的区别。

    <Tabs items="[&#x22;Auth.js&#x22;, &#x22;Better Auth&#x22;]">
      <Tab value="Auth.js">
        ```ts
        "use client"

        import { useSession } from "next-auth/react"

        const { data, status, update } = useSession()
        ```
      </Tab>

      <Tab value="Better Auth">
        ```ts
        "use client"

        import { authClient } from "@/lib/auth-client";

        const { data, error, refetch, isPending, isRefetching } = authClient.useSession()
        ```
      </Tab>
    </Tabs>

    ### 服务端 [#服务端]

    #### 登录 [#登录-1]

    以下是在登录用户时，Auth.js 与 Better Auth 的区别。例如，使用 GitHub OAuth 提供程序：

    <Tabs items="[&#x22;Auth.js&#x22;, &#x22;Better Auth&#x22;]">
      <Tab value="Auth.js">
        ```ts
        import { signIn } from "@/lib/auth"

        await signIn("github")
        ```
      </Tab>

      <Tab value="Better Auth">
        ```ts
        import { auth } from "@/lib/auth";

        const { redirect, url } = await auth.api.signInSocial({
          body: {
            provider: "github",
          },
        })
        ```
      </Tab>
    </Tabs>

    #### 登出 [#登出-1]

    以下是在登出用户时，Auth.js 与 Better Auth 的区别。

    <Tabs items="[&#x22;Auth.js&#x22;, &#x22;Better Auth&#x22;]">
      <Tab value="Auth.js">
        ```ts
        import { signOut } from "@/lib/auth"

        await signOut()
        ```
      </Tab>

      <Tab value="Better Auth">
        ```ts
        import { auth } from "@/lib/auth";
        import { headers } from "next/headers";

        const { success } = await auth.api.signOut({
          headers: await headers(),
        })
        ```
      </Tab>
    </Tabs>

    #### 获取会话 [#获取会话-1]

    以下是在获取当前活跃会话时，Auth.js 与 Better Auth 的区别。

    <Tabs items="[&#x22;Auth.js&#x22;, &#x22;Better Auth&#x22;]">
      <Tab value="Auth.js">
        ```ts
        import { auth } from "@/lib/auth";

        const session = await auth()
        ```
      </Tab>

      <Tab value="Better Auth">
        ```ts
        import { auth } from "@/lib/auth";
        import { headers } from "next/headers";

        const session = await auth.api.getSession({
          headers: await headers(),
        })
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step>
    ## 保护资源 [#保护资源]

    > 代理（中间件）不适用于缓慢的数据获取。虽然代理对于基于权限的重定向等乐观检查可能很有用，但我们建议在每个页面或路由上处理身份验证检查，而不是在代理或布局中。以下是使用 Better Auth 保护资源的基本示例。

    Auth.js 提供了使用代理（中间件）的方法，但我们建议在页面或路由本身上处理身份验证检查，而不是在代理或布局中。以下是使用 Better Auth 保护资源的基本示例。

    <Tabs items="[&#x22;客户端&#x22;, &#x22;服务端&#x22;]">
      <Tab value="客户端">
        ```ts title="app/dashboard/page.tsx"
        "use client";

        import { authClient } from "@/lib/auth-client";
        import { redirect } from "next/navigation";

        const DashboardPage = () => {
          const { data, error, isPending } = authClient.useSession();

          if (isPending) {
            return <div>Pending</div>;
          }
          if (!data || error) {
            redirect("/sign-in");
          }

          return (
            <div>
              <h1>Welcome {data.user.name}</h1>
            </div>
          );
        };

        export default DashboardPage;
        ```
      </Tab>

      <Tab value="服务端">
        ```ts title="app/dashboard/page.tsx"
        import { auth } from "@/lib/auth";
        import { headers } from "next/headers";
        import { redirect } from "next/navigation";

        const DashboardPage = async () => {
          const session = await auth.api.getSession({
            headers: await headers(),
          });

          if (!session) {
            redirect("/sign-in");
          }

          return (
            <div>
              <h1>Welcome {session.user.name}</h1>
            </div>
          );
        };

        export default DashboardPage;

        ```
      </Tab>
    </Tabs>
  </Step>

  <Step>
    ## 数据库模型 [#数据库模型]

    Auth.js 和 Better Auth 都提供无状态（JWT）和数据库会话策略。如果您之前在 Auth.js 中使用了数据库会话策略并计划继续在 Better Auth 中使用它，您还需要迁移数据库。

    就像 Auth.js 有数据库模型一样，Better Auth 也有核心架构。在本节中，我们将对两者进行比较并探索它们之间的差异。

    ### 用户 -> 用户 [#用户---用户]

    <Tabs items="[&#x22;Auth.js&#x22;, &#x22;Better Auth&#x22;]">
      <Tab value="Auth.js">
        <DatabaseTable
          name="User"
          fields="[
{
name: &#x22;id&#x22;,
type: &#x22;string&#x22;,
description: &#x22;每个用户的唯一标识符&#x22;,
isPrimaryKey: true,
},
{
name: &#x22;name&#x22;,
type: &#x22;string&#x22;,
description: &#x22;用户选择的显示名称&#x22;,
isOptional: true,
},
{
name: &#x22;email&#x22;,
type: &#x22;string&#x22;,
description: &#x22;用于通信和登录的用户电子邮件地址&#x22;,
isOptional: true,
},
{
name: &#x22;emailVerified&#x22;,
type: &#x22;Date&#x22;,
description: &#x22;用户电子邮件验证的时间&#x22;,
isOptional: true,
},
{
name: &#x22;image&#x22;,
type: &#x22;string&#x22;,
description: &#x22;用户头像 URL&#x22;,
isOptional: true,
},
]"
        />
      </Tab>

      <Tab value="Better Auth">
        <DatabaseTable
          name="user"
          fields="[
{
name: &#x22;id&#x22;,
type: &#x22;string&#x22;,
description: &#x22;每个用户的唯一标识符&#x22;,
isPrimaryKey: true,
},
{
name: &#x22;name&#x22;,
type: &#x22;string&#x22;,
description: &#x22;用户选择的显示名称&#x22;,
},
{
name: &#x22;email&#x22;,
type: &#x22;string&#x22;,
description: &#x22;用于通信和登录的用户电子邮件地址&#x22;,
isUnique: true,
},
{
name: &#x22;emailVerified&#x22;,
type: &#x22;boolean&#x22;,
description: &#x22;用户电子邮件是否已验证&#x22;,
},
{
name: &#x22;image&#x22;,
type: &#x22;string&#x22;,
description: &#x22;用户头像 URL&#x22;,
isOptional: true,
},
{
name: &#x22;createdAt&#x22;,
type: &#x22;Date&#x22;,
description: &#x22;用户账户创建时间戳&#x22;,
},
{
name: &#x22;updatedAt&#x22;,
type: &#x22;Date&#x22;,
description: &#x22;用户信息最后更新时间戳&#x22;,
},
]"
        />
      </Tab>
    </Tabs>

    ### 会话 -> 会话 [#会话---会话]

    <Tabs items="[&#x22;Auth.js&#x22;, &#x22;Better Auth&#x22;]">
      <Tab value="Auth.js">
        <DatabaseTable
          name="Session"
          fields="[
{
name: &#x22;id&#x22;,
type: &#x22;string&#x22;,
description: &#x22;每个会话的唯一标识符&#x22;,
isPrimaryKey: true,
},
{
name: &#x22;userId&#x22;,
type: &#x22;string&#x22;,
description: &#x22;用户的 ID&#x22;,
isForeignKey: true,
references: { model: &#x22;user&#x22;, field: &#x22;id&#x22;, onDelete: &#x22;cascade&#x22; },
},
{
name: &#x22;sessionToken&#x22;,
type: &#x22;string&#x22;,
description: &#x22;唯一的会话令牌&#x22;,
},
{
name: &#x22;expires&#x22;,
type: &#x22;Date&#x22;,
description: &#x22;会话过期时间&#x22;,
},
]"
        />
      </Tab>

      <Tab value="Better Auth">
        <DatabaseTable
          name="session"
          fields="[
{
name: &#x22;id&#x22;,
type: &#x22;string&#x22;,
description: &#x22;每个会话的唯一标识符&#x22;,
isPrimaryKey: true,
},
{
name: &#x22;userId&#x22;,
type: &#x22;string&#x22;,
description: &#x22;用户的 ID&#x22;,
isForeignKey: true,
references: { model: &#x22;user&#x22;, field: &#x22;id&#x22;, onDelete: &#x22;cascade&#x22; },
},
{
name: &#x22;token&#x22;,
type: &#x22;string&#x22;,
description: &#x22;唯一的会话令牌&#x22;,
isUnique: true,
},
{
name: &#x22;expiresAt&#x22;,
type: &#x22;Date&#x22;,
description: &#x22;会话过期时间&#x22;,
},
{
name: &#x22;ipAddress&#x22;,
type: &#x22;string&#x22;,
description: &#x22;设备的 IP 地址&#x22;,
isOptional: true,
},
{
name: &#x22;userAgent&#x22;,
type: &#x22;string&#x22;,
description: &#x22;设备的用户代理信息&#x22;,
isOptional: true,
},
{
name: &#x22;createdAt&#x22;,
type: &#x22;Date&#x22;,
description: &#x22;会话创建时间戳&#x22;,
},
{
name: &#x22;updatedAt&#x22;,
type: &#x22;Date&#x22;,
description: &#x22;会话更新时间戳&#x22;,
},
]"
        />
      </Tab>
    </Tabs>

    ### 账户 -> 账户 [#账户---账户]

    <Tabs items="[&#x22;Auth.js&#x22;, &#x22;Better Auth&#x22;]">
      <Tab value="Auth.js">
        <DatabaseTable
          name="Account"
          fields="[
{
name: &#x22;id&#x22;,
type: &#x22;string&#x22;,
description: &#x22;每个账户的唯一标识符&#x22;,
isPrimaryKey: true,
},
{
name: &#x22;userId&#x22;,
type: &#x22;string&#x22;,
description: &#x22;用户的 ID&#x22;,
isForeignKey: true,
references: { model: &#x22;user&#x22;, field: &#x22;id&#x22;, onDelete: &#x22;cascade&#x22; },
},
{
name: &#x22;type&#x22;,
type: &#x22;string&#x22;,
description: &#x22;账户类型（例如 'oauth'、'email'、'credentials'）&#x22;,
},
{
name: &#x22;provider&#x22;,
type: &#x22;string&#x22;,
description: &#x22;提供程序标识符&#x22;,
},
{
name: &#x22;providerAccountId&#x22;,
type: &#x22;string&#x22;,
description: &#x22;来自提供程序的账户 ID&#x22;,
},
{
name: &#x22;refresh_token&#x22;,
type: &#x22;string&#x22;,
description: &#x22;账户的刷新令牌，由提供程序返回&#x22;,
isOptional: true,
},
{
name: &#x22;access_token&#x22;,
type: &#x22;string&#x22;,
description: &#x22;账户的访问令牌，由提供程序返回&#x22;,
isOptional: true,
},
{
name: &#x22;expires_at&#x22;,
type: &#x22;number&#x22;,
description: &#x22;访问令牌过期时间&#x22;,
isOptional: true,
},
{
name: &#x22;token_type&#x22;,
type: &#x22;string&#x22;,
description: &#x22;令牌类型&#x22;,
isOptional: true,
},
{
name: &#x22;scope&#x22;,
type: &#x22;string&#x22;,
description: &#x22;账户的作用域，由提供程序返回&#x22;,
isOptional: true,
},
{
name: &#x22;id_token&#x22;,
type: &#x22;string&#x22;,
description: &#x22;来自提供程序的 ID 令牌&#x22;,
isOptional: true,
},
{
name: &#x22;session_state&#x22;,
type: &#x22;string&#x22;,
description: &#x22;OAuth 会话状态&#x22;,
isOptional: true,
},
]"
        />
      </Tab>

      <Tab value="Better Auth">
        <DatabaseTable
          name="account"
          fields="[
{
name: &#x22;id&#x22;,
type: &#x22;string&#x22;,
description: &#x22;每个账户的唯一标识符&#x22;,
isPrimaryKey: true,
},
{
name: &#x22;userId&#x22;,
type: &#x22;string&#x22;,
description: &#x22;用户的 ID&#x22;,
isForeignKey: true,
references: { model: &#x22;user&#x22;, field: &#x22;id&#x22;, onDelete: &#x22;cascade&#x22; },
},
{
name: &#x22;accountId&#x22;,
type: &#x22;string&#x22;,
description: &#x22;由提供程序分配的稳定账户标识符&#x22;,
},
{
name: &#x22;providerId&#x22;,
type: &#x22;string&#x22;,
description: &#x22;用于对账户进行身份验证的提供程序配置&#x22;,
},
{
name: &#x22;accessToken&#x22;,
type: &#x22;string&#x22;,
description: &#x22;账户的访问令牌，由提供程序返回&#x22;,
isOptional: true,
},
{
name: &#x22;refreshToken&#x22;,
type: &#x22;string&#x22;,
description: &#x22;账户的刷新令牌，由提供程序返回&#x22;,
isOptional: true,
},
{
name: &#x22;accessTokenExpiresAt&#x22;,
type: &#x22;Date&#x22;,
description: &#x22;访问令牌过期时间&#x22;,
isOptional: true,
},
{
name: &#x22;refreshTokenExpiresAt&#x22;,
type: &#x22;Date&#x22;,
description: &#x22;刷新令牌过期时间&#x22;,
isOptional: true,
},
{
name: &#x22;scope&#x22;,
type: &#x22;string&#x22;,
description: &#x22;账户的作用域，由提供程序返回&#x22;,
isOptional: true,
},
{
name: &#x22;idToken&#x22;,
type: &#x22;string&#x22;,
description: &#x22;来自提供程序的 ID 令牌&#x22;,
isOptional: true,
},
{
name: &#x22;password&#x22;,
type: &#x22;string&#x22;,
description:
&#x22;账户的密码，主要用于电子邮件和密码身份验证&#x22;,
isOptional: true,
},
{
name: &#x22;createdAt&#x22;,
type: &#x22;Date&#x22;,
description: &#x22;账户创建时间戳&#x22;,
},
{
name: &#x22;updatedAt&#x22;,
type: &#x22;Date&#x22;,
description: &#x22;账户更新时间戳&#x22;,
},
]"
        />
      </Tab>
    </Tabs>

    ### 验证令牌 -> 验证 [#验证令牌---验证]

    <Tabs items="[&#x22;Auth.js&#x22;, &#x22;Better Auth&#x22;]">
      <Tab value="Auth.js">
        <DatabaseTable
          name="VerificationToken"
          fields="[
{
name: &#x22;identifier&#x22;,
type: &#x22;string&#x22;,
description: &#x22;验证请求的标识符&#x22;,
isPrimaryKey: true,
},
{
name: &#x22;token&#x22;,
type: &#x22;string&#x22;,
description: &#x22;验证令牌&#x22;,
isPrimaryKey: true,
},
{
name: &#x22;expires&#x22;,
type: &#x22;Date&#x22;,
description: &#x22;验证令牌过期时间&#x22;,
},
]"
        />
      </Tab>

      <Tab value="Better Auth">
        <DatabaseTable
          name="verification"
          fields="[
{
name: &#x22;id&#x22;,
type: &#x22;string&#x22;,
description: &#x22;每个验证的唯一标识符&#x22;,
isPrimaryKey: true,
},
{
name: &#x22;identifier&#x22;,
type: &#x22;string&#x22;,
description: &#x22;验证请求的标识符&#x22;,
},
{
name: &#x22;value&#x22;,
type: &#x22;string&#x22;,
description: &#x22;待验证的值&#x22;,
},
{
name: &#x22;expiresAt&#x22;,
type: &#x22;Date&#x22;,
description: &#x22;验证请求过期时间&#x22;,
},
{
name: &#x22;createdAt&#x22;,
type: &#x22;Date&#x22;,
description: &#x22;验证请求创建时间戳&#x22;,
},
{
name: &#x22;updatedAt&#x22;,
type: &#x22;Date&#x22;,
description: &#x22;验证请求更新时间戳&#x22;,
},
]"
        />
      </Tab>
    </Tabs>

    ### 对比 [#对比]

    表格: <strong className="underline italic">用户</strong>

    * `name`、`email` 和 `emailVerified` 在 Better Auth 中是必填项，而在 Auth.js 中是可选的
    * Better Auth 中 `emailVerified` 使用布尔值，而 Auth.js 中使用时间戳
    * Better Auth 包含 `createdAt` 和 `updatedAt` 时间戳

    表格: <strong className="underline italic">会话</strong>

    * Better Auth 使用 `token` 而不是 `sessionToken`
    * Better Auth 使用 `expiresAt` 而不是 `expires`
    * Better Auth 包含 `ipAddress` 和 `userAgent` 字段
    * Better Auth 包含 `createdAt` 和 `updatedAt` 时间戳

    表格: <strong className="underline italic">账户</strong>

    * Better Auth 使用驼峰命名法（例如，`refreshToken` 与 `refresh_token`）
    * Better Auth 使用 `id` 表示本地账户记录，并使用 `providerId` 与 `accountId` 组合表示外部身份
    * Better Auth 使用 `providerId` 而不是 `provider`
    * Better Auth 包含用于令牌管理的 `accessTokenExpiresAt` 和 `refreshTokenExpiresAt`
    * Better Auth 包含 `password` 字段，以支持内置凭据身份验证
    * Better Auth 没有 `type` 字段，因为它由 `providerId` 决定
    * Better Auth 移除了 `token_type` 和 `session_state` 字段
    * Better Auth 包含 `createdAt` 和 `updatedAt` 时间戳

    <Callout type="warn">
      **凭据迁移的重要注意事项：**
      在 Auth.js 中，密码通常直接存储在 `User` 模型中。在 Better Auth 中，密码必须存储在 `Account` 表中。
      如果您要迁移带有密码的用户（包括 Phone Number 插件的用户），则**必须**为每位用户在 `account` 表中创建一条记录，其中 `providerId: "credential"`，且 `accountId` 设置为 Better Auth 用户的 `id`。如果没有这条记录，基于密码的登录将会失败。
    </Callout>

    表格: <strong className="underline italic">验证令牌 -> 验证</strong>

    * Better Auth 使用 `Verification` 表代替 `VerificationToken`
    * Better Auth 使用单一主键 `id` 而不是复合主键
    * Better Auth 使用 `value` 而不是 `token` 以支持各种验证类型
    * Better Auth 使用 `expiresAt` 而不是 `expires`
    * Better Auth 包含 `createdAt` 和 `updatedAt` 时间戳

    <Callout type="info">
      如果您使用的是 Auth.js v4，请注意 v5 并没有对数据库架构进行任何破坏性更改。可选字段（如 `oauth_token_secret` 和 `oauth_token`）如果未使用可以删除。不常用的字段（如 `refresh_token_expires_in`）也可以移除。
    </Callout>

    ### 自定义 [#自定义]

    您可能已经扩展了 Auth.js 的数据库模型或实现了其他逻辑。Better Auth 允许您以类型安全的方式自定义核心架构。您还可以定义数据库操作的生命周期期间的自定义逻辑。更多详细信息，请参见[概念 - 数据库](/docs/concepts/database)。
  </Step>
</Steps>

## 总结 [#总结]

您现在已经准备好从 Auth.js 迁移到 Better Auth。有关包含多种身份验证方式的完整实现，请参阅 [Next.js 示例应用](https://github.com/better-auth/better-auth/tree/main/demo/nextjs)。Better Auth 提供了更大的灵活性和更多功能，请务必浏览[文档](/docs)，充分发挥其潜力。

如果您在迁移过程中需要帮助，请加入我们的[社区](/community)，或通过 [contact@better-auth.com](mailto:contact@better-auth.com) 联系我们。

