从 Auth.js 迁移到 Better Auth

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

介绍

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

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

创建 Better Auth 实例

在开始迁移过程之前,在您的项目中设置 Better Auth。请按照安装指南开始。

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

import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
  
export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [GitHub],
})
import { betterAuth } from "better-auth";

export const auth = betterAuth({
  socialProviders: { 
    github: { 
      clientId: process.env.GITHUB_CLIENT_ID!, 
      clientSecret: process.env.GITHUB_CLIENT_SECRET!, 
    }, 
  }, 
})

现在 Better Auth 支持无需任何数据库的无状态会话管理。如果您之前在 Auth.js 中使用了数据库适配器,可以参考下面的数据库模型以了解与 Better Auth 核心架构的差异。

创建客户端实例

此客户端实例包含一组用于与 Better Auth 服务端实例交互的函数。有关更多信息,请参阅客户端文档

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

export const authClient = createAuthClient()

更新路由处理程序

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

import { handlers } from "@/lib/auth"

export const { GET, POST } = handlers
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";

export const { POST, GET } = toNextJsHandler(auth)

会话管理

在本节中,我们将了解 Better Auth 与 Auth.js 相比如何管理会话。有关更多信息,请参阅会话管理文档

客户端

登录

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

"use client"

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

signIn("github")
"use client"

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

const { data, error } = await authClient.signIn.social({
  provider: "github",
})

登出

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

"use client"

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

signOut()
"use client"

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

const { data, error } = await authClient.signOut()

获取会话

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

"use client"

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

const { data, status, update } = useSession()
"use client"

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

const { data, error, refetch, isPending, isRefetching } = authClient.useSession()

服务端

登录

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

import { signIn } from "@/lib/auth"

await signIn("github")
import { auth } from "@/lib/auth";

const { redirect, url } = await auth.api.signInSocial({
  body: {
    provider: "github",
  },
})

登出

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

import { signOut } from "@/lib/auth"

await signOut()
import { auth } from "@/lib/auth";
import { headers } from "next/headers";

const { success } = await auth.api.signOut({
  headers: await headers(),
})

获取会话

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

import { auth } from "@/lib/auth";

const session = await auth()
import { auth } from "@/lib/auth";
import { headers } from "next/headers";

const session = await auth.api.getSession({
  headers: await headers(),
})

保护资源

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

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

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;
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;

数据库模型

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

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

用户 -> 用户

Table
字段
类型
描述
id
string
PK
每个用户的唯一标识符
name ?
string
-
用户选择的显示名称
email ?
string
-
用于通信和登录的用户电子邮件地址
emailVerified ?
Date
-
用户电子邮件验证的时间
image ?
string
-
用户头像 URL
Table
字段
类型
描述
id
string
PK
每个用户的唯一标识符
name
string
-
用户选择的显示名称
email
string
-
用于通信和登录的用户电子邮件地址
emailVerified
boolean
-
用户电子邮件是否已验证
image ?
string
-
用户头像 URL
createdAt
Date
-
用户账户创建时间戳
updatedAt
Date
-
用户信息最后更新时间戳

会话 -> 会话

Table
字段
类型
描述
id
string
PK
每个会话的唯一标识符
userId
string
FK
用户的 ID
sessionToken
string
-
唯一的会话令牌
expires
Date
-
会话过期时间
Table
字段
类型
描述
id
string
PK
每个会话的唯一标识符
userId
string
FK
用户的 ID
token
string
-
唯一的会话令牌
expiresAt
Date
-
会话过期时间
ipAddress ?
string
-
设备的 IP 地址
userAgent ?
string
-
设备的用户代理信息
createdAt
Date
-
会话创建时间戳
updatedAt
Date
-
会话更新时间戳

账户 -> 账户

Table
字段
类型
描述
id
string
PK
每个账户的唯一标识符
userId
string
FK
用户的 ID
type
string
-
账户类型(例如 'oauth'、'email'、'credentials')
provider
string
-
提供程序标识符
providerAccountId
string
-
来自提供程序的账户 ID
refresh_token ?
string
-
账户的刷新令牌,由提供程序返回
access_token ?
string
-
账户的访问令牌,由提供程序返回
expires_at ?
number
-
访问令牌过期时间
token_type ?
string
-
令牌类型
scope ?
string
-
账户的作用域,由提供程序返回
id_token ?
string
-
来自提供程序的 ID 令牌
session_state ?
string
-
OAuth 会话状态
Table
字段
类型
描述
id
string
PK
每个账户的唯一标识符
userId
string
FK
用户的 ID
accountId
string
-
由 SSO 提供的账户 ID 或等于 userId 的凭证账户 ID
providerId
string
-
提供程序的 ID
accessToken ?
string
-
账户的访问令牌,由提供程序返回
refreshToken ?
string
-
账户的刷新令牌,由提供程序返回
accessTokenExpiresAt ?
Date
-
访问令牌过期时间
refreshTokenExpiresAt ?
Date
-
刷新令牌过期时间
scope ?
string
-
账户的作用域,由提供程序返回
idToken ?
string
-
来自提供程序的 ID 令牌
password ?
string
-
账户的密码,主要用于电子邮件和密码身份验证
createdAt
Date
-
账户创建时间戳
updatedAt
Date
-
账户更新时间戳

验证令牌 -> 验证

Table
字段
类型
描述
identifier
string
PK
验证请求的标识符
token
string
PK
验证令牌
expires
Date
-
验证令牌过期时间
Table
字段
类型
描述
id
string
PK
每个验证的唯一标识符
identifier
string
-
验证请求的标识符
value
string
-
待验证的值
expiresAt
Date
-
验证请求过期时间
createdAt
Date
-
验证请求创建时间戳
updatedAt
Date
-
验证请求更新时间戳

对比

表格: 用户

  • nameemailemailVerified 在 Better Auth 中是必填项,而在 Auth.js 中是可选的
  • Better Auth 中 emailVerified 使用布尔值,而 Auth.js 中使用时间戳
  • Better Auth 包含 createdAtupdatedAt 时间戳

表格: 会话

  • Better Auth 使用 token 而不是 sessionToken
  • Better Auth 使用 expiresAt 而不是 expires
  • Better Auth 包含 ipAddressuserAgent 字段
  • Better Auth 包含 createdAtupdatedAt 时间戳

表格: 账户

  • Better Auth 使用驼峰命名法(例如 refreshToken 而不是 refresh_token
  • Better Auth 包含 accountId 以区分账户 ID 和内部 ID
  • Better Auth 使用 providerId 而不是 provider
  • Better Auth 包含 accessTokenExpiresAtrefreshTokenExpiresAt 用于令牌管理
  • Better Auth 包含 password 字段以支持内置的凭证身份验证
  • Better Auth 不包含 type 字段,因为它由 providerId 决定
  • Better Auth 移除了 token_typesession_state 字段
  • Better Auth 包含 createdAtupdatedAt 时间戳

关于凭证迁移的关键提示: 在 Auth.js 中,密码通常直接存储在 User 模型中。在 Better Auth 中,密码必须存储在 Account 表中。 如果您要迁移带有密码的用户(包括用于电话号码插件),您必须为每个用户创建一个 account 表中的记录,并将 providerId 设置为 "credential"。没有此记录,基于密码的登录将失败。

表格: 验证令牌 -> 验证

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

如果您使用的是 Auth.js v4,请注意 v5 并没有对数据库架构进行任何破坏性更改。可选字段(如 oauth_token_secretoauth_token)如果未使用可以删除。不常用的字段(如 refresh_token_expires_in)也可以移除。

自定义

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

Summary

You’re now ready to migrate from Auth.js to Better Auth. For a full implementation featuring multiple authentication methods, please refer to the Next.js demo app. Better Auth offers greater flexibility and more features, so be sure to explore the documentation to make the most of its full potential.

If you need help during the migration, please join our community or contact us at contact@better-auth.com.