# 从 WorkOS 迁移到 Better Auth (/docs/guides/workos-migration-guide)

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



本指南将带您了解如何将项目从 WorkOS 迁移到 Better Auth，涵盖如何迁移一个与 Next.js 应用集成的基础 WorkOS 设置及关键注意事项。

## 开始前 [#开始前]

开始之前，让我们回顾一下哪些 WorkOS 认证功能在 Better Auth 中得到了完全或部分支持。如果您在 WorkOS 中使用的某个功能是通过插件提供的，您需要在下一步进行配置。

<Accordions type="multiple">
  <Accordion title="🟢 已支持">
    <>
      | 来自 WorkOS | Better Auth 中的对应功能                                     |
      | --------- | ------------------------------------------------------ |
      | 单点登录      | 使用 [SSO 插件](/docs/plugins/sso)。                        |
      | 邮箱 + 密码   | 内置支持。                                                  |
      | 通行密钥      | 使用 [Passkey 插件](/docs/plugins/passkey)。                |
      | 社交登录      | 内置支持，并且支持更多提供商。                                        |
      | 多因素认证     | 使用 [双因素插件](/docs/plugins/2fa)。                         |
      | 魔法认证      | 使用 [魔法链接插件](/docs/plugins/magic-link)。                 |
      | CLI 认证    | 使用 [设备授权插件](/docs/plugins/device-authorization)。       |
      | API 密钥    | 使用 [API 密钥插件](/docs/plugins/api-key)。                  |
      | 自定义邮件     | 完全可定制。                                                 |
      | 目录预配      | 使用 [SCIM 插件](/docs/plugins/scim)。                      |
      | 域名验证      | 使用 [SSO 插件](/docs/plugins/sso)。                        |
      | 邮箱验证      | 内置支持。                                                  |
      | 身份关联      | 内置支持。                                                  |
      | 冒充        | 使用 [Admin 插件](/docs/plugins/admin)。                    |
      | JWT 模板    | 使用 [JWT 插件](/docs/plugins/jwt)。                        |
      | 元数据外部 ID  | 可以通过[扩展核心 schema](/docs/concepts/database#扩展核心架构)自由添加。 |
      | 角色和权限     | 使用 [组织插件](/docs/plugins/organization)。                 |
    </>
  </Accordion>

  <Accordion title="🟡 部分支持">
    <>
      | 来自 WorkOS | Better Auth 中的对应功能                                                                        |
      | --------- | ----------------------------------------------------------------------------------------- |
      | 即时预配      | 通过 [SSO 插件](/docs/plugins/sso) 部分支持。                                                      |
      | 邀请        | 没有现成的仪表板可用，但可以使用 [Admin 插件](/docs/plugins/admin) + [组织插件](/docs/plugins/organization) 实现。 |
      | 组织策略      | 部分支持，但可以通过 [SSO 插件](/docs/plugins/sso) + [组织插件](/docs/plugins/organization) 的 hooks 完整实现。 |
    </>
  </Accordion>
</Accordions>

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

    首先，在您的项目中设置 Better Auth。请按照[安装指南](/docs/installation)开始使用。

    ### 数据库 [#数据库]

    Better Auth 支持多种数据库。请设置您偏好的数据库。本指南中，我们将使用 PostgreSQL 和默认数据库适配器。

    ```ts title="auth.ts"
    import { betterAuth } from "better-auth";
    import { Pool } from "pg";

    export const auth = betterAuth({
      database: new Pool({ // [!code highlight]
        connectionString: process.env.DATABASE_URL, // [!code highlight]
      }), // [!code highlight]
    });
    ```

    ### 邮箱 & 密码 [#邮箱--密码]

    按如下方式启用邮箱和密码认证。由于 WorkOS 默认会验证每个用户的邮箱，因此此设置与默认行为类似。如有需要，您可以进行调整。更多信息请参阅[邮箱/密码认证文档](/docs/authentication/email-password)。

    ```ts
    import { betterAuth } from "better-auth";
    import { Pool } from "pg";

    export const auth = betterAuth({
      database: new Pool({
        connectionString: process.env.DATABASE_URL,
      }),
      emailAndPassword: { // [!code highlight]
        enabled: true, // [!code highlight]
        requireEmailVerification: true, // [!code highlight]
        minPasswordLength: 10, // [!code highlight]
        sendResetPassword: async ({ user, url, token }, request) => { // [!code highlight]
          // 实现您的邮件发送逻辑 // [!code highlight]
        }, // [!code highlight]
      }, // [!code highlight]
      emailVerification: { // [!code highlight]
        sendVerificationEmail: async ({ user, url, token }, request) => { // [!code highlight]
          // 实现您的邮件发送逻辑 // [!code highlight]
        }, // [!code highlight]
      }, // [!code highlight]
    });
    ```

    ### 社交提供商（可选） [#社交提供商可选]

    按如下方式设置您在 WorkOS 中使用的社交提供商。Better Auth 支持更广泛的提供商，因此如果需要，您可以添加更多。由于 WorkOS 会确保邮箱唯一，请在 Better Auth 中配置 `account.accountLinking` 以确保相同的行为。

    ```ts
    import { betterAuth } from "better-auth";
    import { Pool } from "pg";

    export const auth = betterAuth({
      // ... other options

      socialProviders: { // [!code highlight]
        github: { // [!code highlight]
          clientId: process.env.GITHUB_CLIENT_ID!, // [!code highlight]
          clientSecret: process.env.GITHUB_CLIENT_SECRET!, // [!code highlight]
        }, // [!code highlight]
        // ... 其他提供商 // [!code highlight]
      }, // [!code highlight]
      account: { // [!code highlight]
        accountLinking: { // [!code highlight]
          enabled: true, // [!code highlight]
          trustedProviders: ["email-password", "github"], // [!code highlight]
        }, // [!code highlight]
      }, // [!code highlight]
    });
    ```

    ### 其他字段 [#其他字段]

    {/* cspell:disable-next-line */}

    您很可能在 WorkOS 中使用了 metadata。为了保留这些 metadata 以及来自 WorkOS 的用户 id（例如 user\_01KBT4BMFF7ASGRDD0WZ6W63FF），请如下扩展 `user` schema。Better Auth 提供了更灵活的方式来存储用户数据。更多信息请参阅[扩展核心 schema 文档](/docs/concepts/database#扩展核心架构)。

    ```ts title="auth.ts"
    import { betterAuth } from "better-auth";
    import { Pool } from "pg";

    export const auth = betterAuth({
      // ... other options
      
      user: { // [!code highlight]
        additionalFields: { // [!code highlight]
          metadata: { // [!code highlight]
            type: "json", // [!code highlight]
            required: false, // [!code highlight]
            defaultValue: null, // [!code highlight]
          }, // [!code highlight]
        }, // [!code highlight]
      }, // [!code highlight]
    });
    ```

    ### 插件 [#插件]

    请参考上方将 WorkOS 功能映射到 Better Auth 的[章节](#before-we-begin)。如果您在 WorkOS 中使用的某个功能在 Better Auth 中作为插件提供，请将其添加到插件选项中。Better Auth 通过插件提供了更广泛的开箱即用功能。更多信息请参阅[插件文档](/docs/concepts/plugins)。

    ```ts title="auth.ts"
    import { betterAuth } from "better-auth";
    import { haveIBeenPwned } from "better-auth/plugins/haveibeenpwned";
    import { Pool } from "pg";

    export const auth = betterAuth({
      // ... other options
      
      plugins: [ // [!code highlight]
        haveIBeenPwned() // [!code highlight]
        // ... 其他插件 // [!code highlight]
      ], // [!code highlight]
    });
    ```

    <Callout type="info">
      如果您依赖超出基础邮箱+密码和社交登录之外的高级 WorkOS 功能，请参考上方的功能映射来配置相应的插件。
    </Callout>
  </Step>

  <Step>
    ## 生成 Schema [#生成-schema]

    Better Auth 允许您控制自己的数据库，并且可以通过 CLI 轻松为您的 auth 实例生成合适的 schema。更多信息请参阅[CLI 文档](/docs/concepts/cli)。

    ### 默认数据库适配器 [#默认数据库适配器]

    运行 `migrate` 命令，在数据库中为您的 Better Auth 实例创建 schema。

    <CodeBlockTabs defaultValue="npm" groupId="persist-install">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="npm">
          npm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="pnpm">
          pnpm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="yarn">
          yarn
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="bun">
          bun
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="npm">
        ```bash
        npx auth migrate
        ```
      </CodeBlockTab>

      <CodeBlockTab value="pnpm">
        ```bash
        pnpm dlx auth migrate
        ```
      </CodeBlockTab>

      <CodeBlockTab value="yarn">
        ```bash
        yarn dlx auth migrate
        ```
      </CodeBlockTab>

      <CodeBlockTab value="bun">
        ```bash
        bun x auth migrate
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    ### 其他数据库适配器 [#其他数据库适配器]

    如果您使用的是 Prisma 或 Drizzle 之类的数据库适配器，请使用 `generate` 命令为您的 ORM 创建 schema。之后，使用 Drizzle Kit 等外部工具运行迁移。

    <CodeBlockTabs defaultValue="npm" groupId="persist-install">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="npm">
          npm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="pnpm">
          pnpm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="yarn">
          yarn
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="bun">
          bun
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="npm">
        ```bash
        npx auth generate
        ```
      </CodeBlockTab>

      <CodeBlockTab value="pnpm">
        ```bash
        pnpm dlx auth generate
        ```
      </CodeBlockTab>

      <CodeBlockTab value="yarn">
        ```bash
        yarn dlx auth generate
        ```
      </CodeBlockTab>

      <CodeBlockTab value="bun">
        ```bash
        bun x auth generate
        ```
      </CodeBlockTab>
    </CodeBlockTabs>
  </Step>

  <Step>
    ## 迁移脚本 [#迁移脚本]

    ### 创建迁移脚本 [#创建迁移脚本]

    创建一个迁移脚本，将您的用户数据从 WorkOS 导入到数据库中。

    ```ts title="scripts/migration.ts"
    import { auth } from "@/lib/auth"; // 您的 auth 实例路径
    import { WorkOS } from "@workos-inc/node";

    //==============================================================================

    /*
      限流配置

      WorkOS 读取 API：每 10 秒 1,000 次请求
      默认设置：使用限制的 80%，以避免边缘情况

      参考：https://workos.com/docs/reference/rate-limits
    */
    const TIME_WINDOW_MS = 10 * 1000; // 时间窗口（毫秒，10 秒）
    const MAX_REQUESTS_PER_WINDOW = 800; // 每个时间窗口允许的最大 API 调用次数
    const USERS_PER_REQUEST = 100; // 每次 API 调用获取多少用户

    //==============================================================================

    if (!process.env.WORKOS_API_KEY || !process.env.WORKOS_CLIENT_ID) {
      throw new Error(
        "缺少必需的环境变量 WORKOS_API_KEY 和/或 WORKOS_CLIENT_ID",
      );
    }
    const workos = new WorkOS(process.env.WORKOS_API_KEY);

    /**
     * 创建一个速率限制器来跟踪和控制请求速率
     */
    const createRateLimiter = (maxRequests: number, windowMs: number) => {
      let requestTimestamps: number[] = [];

      const waitIfNeeded = async (): Promise<void> => {
        const now = Date.now();

        // 移除当前窗口之外的时间戳
        requestTimestamps = requestTimestamps.filter(
          (timestamp) => now - timestamp < windowMs,
        );

        // 如果达到限制，则计算等待时间
        if (requestTimestamps.length >= maxRequests) {
          const oldestTimestamp = requestTimestamps[0]!;
          const waitTime = windowMs - (now - oldestTimestamp) + 1000; // 1 秒缓冲

          console.log(
            `⏳ 节流中（已使用 ${requestTimestamps.length}/${maxRequests} 次调用）。等待 ${Math.ceil(waitTime / 1000)} 秒...`,
          );
          await new Promise((resolve) => setTimeout(resolve, waitTime));

          // 等待后清理旧时间戳
          const newNow = Date.now();
          requestTimestamps = requestTimestamps.filter(
            (timestamp) => newNow - timestamp < windowMs,
          );
        }

        // 记录本次请求
        requestTimestamps.push(Date.now());
      };

      const getStats = (): {
        current: number;
        max: number;
        windowMinutes: number;
      } => {
        const now = Date.now();
        requestTimestamps = requestTimestamps.filter(
          (timestamp) => now - timestamp < windowMs,
        );

        return {
          current: requestTimestamps.length,
          max: maxRequests,
          windowMinutes: windowMs / (60 * 1000),
        };
      };

      return { waitIfNeeded, getStats };
    };

    /**
     * 安全地将各种日期格式转换为 Date 对象。
     * 如果转换失败则返回当前日期（适用于 createdAt/updatedAt）。
     */
    const safeDateConversion = (date?: string | number | Date | null): Date => {
      if (date == null) return new Date();

      if (date instanceof Date) return new Date(date.getTime());

      if (typeof date === "number") {
        if (!Number.isFinite(date)) return new Date();
        return new Date(date);
      }

      if (typeof date === "string") {
        const trimmed = date.trim();
        if (trimmed === "") return new Date();
        const parsed = new Date(trimmed);
        if (isNaN(parsed.getTime())) return new Date();
        return parsed;
      }

      return new Date();
    };

    /**
     * 安全地将 firstName 和 lastName 转换为完整姓名字符串。
     * 如果两个名字都为空，则返回 "Username"。
     */
    const safeNameConversion = (
      firstName?: string | null,
      lastName?: string | null,
    ): string => {
      const trimmedFirstName = firstName?.trim();
      const trimmedLastName = lastName?.trim();

      if (trimmedFirstName && trimmedLastName) {
        return `${trimmedFirstName} ${trimmedLastName}`;
      }

      if (trimmedFirstName) return trimmedFirstName;
      if (trimmedLastName) return trimmedLastName;

      return "Username";
    };

    async function migrateFromWorkOS() {
      const ctx = await auth.$context;
      const rateLimiter = createRateLimiter(
        MAX_REQUESTS_PER_WINDOW,
        TIME_WINDOW_MS,
      );

      let totalUsers = 0;
      let migratedUsers = 0;
      let skippedUsers = 0;
      let failedUsers = 0;

      let hasMoreUsers = true;
      let after: string | undefined;
      let batchCount = 0;

      console.log("");
      console.log("=".repeat(40));
      console.log("🚀 开始迁移");
      console.log("");
      console.log(`Settings:`);
      console.log(
        ` - Max API calls: ${MAX_REQUESTS_PER_WINDOW} per ${TIME_WINDOW_MS / 1000}s`,
      );
      console.log(` - Users per call: ${USERS_PER_REQUEST}`);
      console.log("=".repeat(40));
      console.log("");

      while (hasMoreUsers) {
        try {
          await rateLimiter.waitIfNeeded();

          const workosUserList = await workos.userManagement.listUsers({
            limit: USERS_PER_REQUEST,
            after,
          });

          batchCount++;
          console.log(
            `📦 批次 ${batchCount}：从 WorkOS 获取了 ${workosUserList.data.length} 位用户`,
          );

          after = workosUserList.listMetadata.after || undefined;
          hasMoreUsers = !!after;
          totalUsers += workosUserList.data.length;

          for (const workosUser of workosUserList.data) {
            try {
              console.log(`\n正在处理用户：${workosUser.email}`);

              // 按邮箱检查用户是否已存在
              // WorkOS 通过邮箱验证流程确保所有用户邮箱唯一
              const existingUser = await ctx.adapter.findOne<
                typeof auth.$Infer.Session.user
              >({
                model: "user",
                where: [
                  {
                    field: "email",
                    value: workosUser.email,
                  },
                ],
              });

              if (existingUser) {
                console.log(
                  `🟡 用户已存在，跳过：${workosUser.email}`,
                );
                skippedUsers++;
                continue;
              }

              // 创建用户
              await ctx.adapter.create<typeof auth.$Infer.Session.user>({
                model: "user",
                data: {
                  email: workosUser.email,
                  emailVerified: workosUser.emailVerified,
                  image: workosUser.profilePictureUrl,
                  name: safeNameConversion(
                    workosUser.firstName,
                    workosUser.lastName,
                  ),
                  createdAt: safeDateConversion(workosUser.createdAt),
                  updatedAt: safeDateConversion(workosUser.updatedAt),
                  metadata: {
                    workosId: workosUser.id,
                    ...(workosUser.metadata || {}),
                  },
                },
              });

              console.log(`🟢 已迁移用户 ${workosUser.email}`);
              migratedUsers++;
            } catch (error) {
              console.error(
                `🔴 迁移用户 ${workosUser.email} 失败\n`,
                error,
              );
              failedUsers++;
            }
          }

          console.log("");
        } catch (error) {
          console.error("🚨 获取批次时出错：", error);
          throw error;
        }
      }

      console.log("");
      console.log("=".repeat(40));
      console.log("📝 迁移摘要");
      console.log(`处理的用户总数：${totalUsers}`);
      console.log("");
      console.log(`🔴 失败：${failedUsers}`);
      console.log(`🟡 跳过：${skippedUsers}`);
      console.log(`🟢 成功迁移：${migratedUsers}`);
      console.log("=".repeat(40));
    }

    async function main() {
      try {
        await migrateFromWorkOS();
        process.exit(0);
      } catch (error) {
        console.error("\n迁移失败：", error);
        process.exit(1);
      }
    }
    main();
    ```

    <Callout type="info">
      **注意**

      * 从 WorkOS 获取用户数据时，您需要使用其 API，而该 API 受限流限制。示例脚本包含基础配置，请根据您的环境进行调整。
      * 此迁移脚本涵盖了使用邮箱+密码和社交登录管理用户的常见情况。对于 SSO 或 CLI Auth 等在 Better Auth 中以插件提供的功能，请务必根据示例更新脚本。
    </Callout>

    ### 运行迁移脚本 [#运行迁移脚本]

    ```bash title="Terminal"
    bun scripts/migration.ts # 或使用 node、ts-node 等
    ```

    🎉 现在您已经将用户数据迁移到数据库中了，接下来让我们看看如何更新应用逻辑。
  </Step>

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

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

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

    export const authClient = createAuthClient({
        plugins: [
            // 如有需要，添加需要客户端的插件
        ]
    });
    ```
  </Step>

  <Step>
    ## 创建 API 路由 [#创建-api-路由]

    在 WorkOS 中，auth API 是作为托管服务提供的。而在 Better Auth 中，auth API 现在直接位于您的应用中。

    ```ts title="/app/api/auth/[...all]/route.ts"
    import { auth } from "@/lib/auth";
    import { toNextJsHandler } from "better-auth/next-js";

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

  <Step>
    ## 登录/注册页面 [#登录注册页面]

    在 WorkOS 中，您可能会像这样获取并使用 URL。

    ```ts
    const signInUrl = await getSignInUrl();
    const signUpUrl = await getSignUpUrl();
    ```

    在 Better Auth 中，不再通过 API 获取这些值，而是可以在您想要的路径上创建页面并直接使用它们。
  </Step>

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

    > Proxy（中间件）并不适合进行缓慢的数据获取。虽然 Proxy 可用于基于权限重定向等乐观检查，但不应将其用作完整的会话管理或授权解决方案。- [Next.js 文档](https://nextjs.org/docs/app/getting-started/proxy#use-cases)

    ### 中间件认证 [#中间件认证]

    WorkOS 提供 Proxy（中间件）认证。Better Auth 不建议直接在中间件中保护资源，因此我们没有提供专门的辅助工具。

    ```ts title="proxy.ts / middleware.ts"
    import { authkitMiddleware } from '@workos-inc/authkit-nextjs';

    export default authkitMiddleware({
      middlewareAuth: {
        enabled: true,
        unauthenticatedPaths: ['/'],
      },
    });

    export const config = { matcher: ['/', '/account/:page*'] };
    ```

    在 Better Auth 中，为了方便而非资源保护，可以按如下方式使用 proxy（中间件）。这在 Next.js 15+ 的 Node.js 运行时中受支持。

    ```ts title="proxy.ts"
    import { NextRequest, NextResponse } from "next/server";
    import { headers } from "next/headers";
    import { auth } from "@/lib/auth";

    export async function proxy(request: NextRequest) {
        const session = await auth.api.getSession({
            headers: await headers()
        })

        // 这是推荐的乐观重定向用户的方法
        // 我们建议在每个页面/路由中处理认证检查
        if(!session) {
            return NextResponse.redirect(new URL("/sign-in", request.url));
        }

        return NextResponse.next();
    }

    export const config = {
      matcher: ["/dashboard"], // 指定中间件适用的路由
    };
    ```

    ### 基于页面的认证 [#基于页面的认证]

    在 WorkOS 中，如果资源是在每个页面上进行保护的，您可以在 Better Auth 中按如下方式更新逻辑。

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

    <Tabs items="[&#x22;WorkOS&#x22;, &#x22;Better Auth&#x22;]">
      <Tab value="WorkOS">
        ```ts title="app/dashboard/page.tsx"
        import { withAuth } from "@workos-inc/authkit-nextjs";

        export default async function DashboardPage() {
          const { user } = await withAuth({ ensureSignedIn: true });

          return (
            <div>
              <p>Welcome {user.firstName && `, ${user.lastName}`}</p>
            </div>
          );
        }
        ```
      </Tab>

      <Tab value="Better Auth">
        ```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>
              <p>Welcome {session.user.name}</p>
            </div>
          );
        };

        export default DashboardPage;
        ```
      </Tab>
    </Tabs>

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

    <Tabs items="[&#x22;WorkOS&#x22;, &#x22;Better Auth&#x22;]">
      <Tab value="WorkOS">
        ```ts title="app/dashboard/page.tsx"
        "use client";

        import { useAuth } from "@workos-inc/authkit-nextjs/components";

        export default function HomePage() {
          const { user, loading } = useAuth({ ensureSignedIn: true });

          if (loading) {
            return <div>Loading...</div>;
          }

          return (
            <div>
              <p>Welcome {user.firstName && `, ${user.lastName}`}</p>
            </div>
          );
        }

        ```
      </Tab>

      <Tab value="Better Auth">
        ```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>
              <p>Welcome {data.user.name}</p>
            </div>
          );
        };

        export default DashboardPage;
        ```
      </Tab>
    </Tabs>

    <Callout type="info">
      如果像 `ensureSignedIn` 这样的选项在 WorkOS 中很方便，您可以在 Better Auth 中创建一个可复用的辅助函数，例如 `ensureSession()`。
    </Callout>
  </Step>

  <Step>
    ## 移除 WorkOS 依赖 [#移除-workos-依赖]

    在确认一切正常后，移除 WorkOS 依赖：

    <CodeBlockTabs defaultValue="npm" groupId="persist-install">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="npm">
          npm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="pnpm">
          pnpm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="yarn">
          yarn
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="bun">
          bun
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="npm">
        ```bash
        npm uninstall @workos-inc/node @workos-inc/authkit-nextjs
        ```
      </CodeBlockTab>

      <CodeBlockTab value="pnpm">
        ```bash
        pnpm remove @workos-inc/node @workos-inc/authkit-nextjs
        ```
      </CodeBlockTab>

      <CodeBlockTab value="yarn">
        ```bash
        yarn remove @workos-inc/node @workos-inc/authkit-nextjs
        ```
      </CodeBlockTab>

      <CodeBlockTab value="bun">
        ```bash
        bun remove @workos-inc/node @workos-inc/authkit-nextjs
        ```
      </CodeBlockTab>
    </CodeBlockTabs>
  </Step>
</Steps>

## 注意事项 [#注意事项]

<strong className="underline italic">
  密码哈希
</strong>

如果您管理的用户使用邮箱 + 密码系统，则 WorkOS 目前不支持导出密码哈希。迁移后，用户需要在您的认证系统中重置密码。请务必在迁移前后充足时间内通知用户此变更。

<strong className="underline italic">
  数据同步
</strong>

WorkOS 作为托管服务，通过 API 或 Webhook 与您的服务器保持数据同步。Better Auth 则由您自行完全掌控认证系统，通过 API 自由管理数据。但如果之前依赖 Webhook 实现同步，则需要额外调整。

<strong className="underline italic">
  停机时间
</strong>

WorkOS 通过其 API 暴露数据，但有限制，如不支持导出密码哈希。基于这些限制，实现零停机迁移较为困难。请规划好迁移流程，预留足够缓冲时间，并告知用户预期影响。

<strong className="underline italic">
  活跃会话
</strong>

现有活跃会话不会被迁移。迁移后用户需重新登录，务必提前通知用户。

## 总结 [#总结]

恭喜！您已成功完成从 WorkOS 迁移至 Better Auth。Better Auth 提供了更高的灵活性和更多功能，务必深入探索[文档](/docs)以发挥其全部潜力。

如果您需要迁移帮助，请加入我们的[社区](/community)或联系[企业支持](/enterprise)。

