从 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)

会话管理

本节将对比 Auth.js 和 Better Auth 中的会话管理方式。详情请参见此处

客户端

登入

以下是使用 GitHub OAuth 提供商时,Auth.js 与 Better Auth 登录用户的差异示例:

"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()

服务器端

登入

以下是使用 GitHub OAuth 提供商时,Auth.js 和 Better Auth 登录用户的差异示例:

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(),
})

保护资源

Proxy(中间件)并非为慢速数据获取设计。虽然 Proxy 可用于权限检查类的乐观性校验(如基于权限的重定向),但不应作为完整的会话管理或授权解决方案。- Next.js 文档

Auth.js 提供了使用 Proxy(中间件)的方式,但我们建议在每个页面或路由里进行身份验证检查,而非在 Proxy 或 Layout 中。以下是使用 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>加载中</div>;
  }
  if (!data || error) {
    redirect("/sign-in");
  }

  return (
    <div>
      <h1>欢迎 {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>欢迎 {session.user.name}</h1>
    </div>
  );
};

export default DashboardPage;

数据库模型

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

正如 Auth.js 有数据库模型,Better Auth 也有核心模式。本节将对比两者并探讨差异。

User -> User

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

Session -> Session

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
会话更新时间戳

Account -> Account

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 相同
providerId
string
提供商 ID
accessToken
string
?
账号的访问令牌,由提供商返回
refreshToken
string
?
账号的刷新令牌,由提供商返回
accessTokenExpiresAt
Date
?
访问令牌过期时间
refreshTokenExpiresAt
Date
?
刷新令牌过期时间
scope
string
?
账号权限范围,由提供商返回
idToken
string
?
提供商返回的 ID 令牌
password
string
?
账号密码,主要用于邮件和密码认证
createdAt
Date
账号创建时间戳
updatedAt
Date
账号更新时间戳

VerificationToken -> Verification

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

比较总结

表:User

  • Better Auth 中 nameemailemailVerified 字段为必填,Auth.js 中为可选
  • Better Auth 使用布尔值表示 emailVerified,而 Auth.js 使用时间戳
  • Better Auth 增加了 createdAtupdatedAt 时间戳字段

表:Session

  • Better Auth 使用 token 替代 sessionToken
  • Better Auth 使用 expiresAt 替代 expires
  • Better Auth 增加了 ipAddressuserAgent 字段
  • Better Auth 包括 createdAtupdatedAt 时间戳

表:Account

  • Better Auth 采用 camelCase 命名(如 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 时间戳

表:VerificationToken -> Verification

  • 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 允许您以类型安全的方式自定义核心模式。您还可以在数据库操作的生命周期中定义自定义逻辑。更多详情请参见概念 - 数据库

总结

现在,您已准备好从 Auth.js 迁移到 Better Auth。欲了解包含多种认证方式的完整实现,请访问 Next.js Demo 应用。Better Auth 提供更灵活和更多功能,请务必探索文档以充分发挥其潜力。

如果您需要迁移帮助,欢迎加入我们的社区或联系 contact@better-auth.com