从 Supabase Auth 迁移到 Better Auth

在本指南中,我们将逐步介绍将项目从 Supabase Auth 迁移到 Better Auth 的步骤。

此迁移将使所有活跃会话失效。虽然本指南目前未涵盖迁移双因素 (2FA) 或行级安全 (RLS) 配置,但通过额外步骤两者均可实现。

在开始之前

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

连接到您的数据库

您需要连接到数据库以迁移用户和账户。从您的 Supabase 项目中复制 DATABASE_URL,并使用它连接到数据库。对于本示例,我们需要安装 pg 来连接数据库。

npm install pg

然后,您可以使用以下代码连接到数据库。

auth.ts
import { Pool } from "pg";

export const auth = betterAuth({
    database: new Pool({ 
        connectionString: process.env.DATABASE_URL 
    }),
})

启用电子邮件和密码(可选)

在您的认证配置中启用电子邮件和密码。

auth.ts
import { admin, anonymous } from "better-auth/plugins";

export const auth = betterAuth({
    database: new Pool({ 
        connectionString: process.env.DATABASE_URL 
    }),
	emailVerification: {
		sendEmailVerification: async(user)=>{
			// 发送电子邮件验证邮件
			// 在此处实现您自己的逻辑
		}
	},
    emailAndPassword: { 
        enabled: true, 
    } 
})

设置社交提供程序(可选)

在您的认证配置中添加您在 Supabase 项目中已启用的社交提供程序。

auth.ts
import { admin, anonymous } from "better-auth/plugins";

export const auth = betterAuth({
    database: new Pool({ 
        connectionString: process.env.DATABASE_URL 
    }),
    emailAndPassword: { 
        enabled: true,
    },
    socialProviders: { 
        github: { 
            clientId: process.env.GITHUB_CLIENT_ID, 
            clientSecret: process.env.GITHUB_CLIENT_SECRET, 
        } 
    } 
})

添加管理员和匿名插件(可选)

在您的认证配置中添加 管理员匿名 插件。

auth.ts
import { admin, anonymous } from "better-auth/plugins";

export const auth = betterAuth({
    database: new Pool({ 
        connectionString: process.env.DATABASE_URL 
    }),
    emailAndPassword: { 
        enabled: true,
    },
    socialProviders: {
        github: {
            clientId: process.env.GITHUB_CLIENT_ID!,
            clientSecret: process.env.GITHUB_CLIENT_SECRET!,
        }
    },
    plugins: [admin(), anonymous()], 
})

运行迁移

运行迁移以在您的数据库中创建必要的表。

Terminal
npx @better-auth/cli migrate

这将在您的数据库中创建以下表:

这些表将创建在 public 架构中。

复制迁移脚本

现在我们的数据库中已有必要的表,我们可以运行迁移脚本来将用户和账户从 Supabase 迁移到 Better Auth。

首先在您的项目中创建一个 .ts 文件。

Terminal
touch migration.ts

然后将以下代码复制并粘贴到文件中。

migration.ts
import { Pool } from "pg";
import { auth } from "./auth";
import { User as SupabaseUser } from "@supabase/supabase-js";

type User = SupabaseUser & {
	is_super_admin: boolean;
	raw_user_meta_data: {
		avatar_url: string;
	};
	encrypted_password: string;
	email_confirmed_at: string;
	created_at: string;
	updated_at: string;
	is_anonymous: boolean;
	identities: {
		provider: string;
		identity_data: {
			sub: string;
			email: string;
		};
		created_at: string;
		updated_at: string;
	};
};

const migrateFromSupabase = async () => {
	const ctx = await auth.$context;
	const db = ctx.options.database as Pool;
	const users = await db
		.query(`
			SELECT 
				u.*,
				COALESCE(
					json_agg(
						i.* ORDER BY i.id
					) FILTER (WHERE i.id IS NOT NULL),
					'[]'::json
				) as identities
			FROM auth.users u
			LEFT JOIN auth.identities i ON u.id = i.user_id
			GROUP BY u.id
		`)
		.then((res) => res.rows as User[]);
	for (const user of users) {
		if (!user.email) {
			continue;
		}
		await ctx.adapter
			.create({
				model: "user",
				data: {
					id: user.id,
					email: user.email,
					name: user.email,
					role: user.is_super_admin ? "admin" : user.role,
					emailVerified: !!user.email_confirmed_at,
					image: user.raw_user_meta_data.avatar_url,
					createdAt: new Date(user.created_at),
					updatedAt: new Date(user.updated_at),
					isAnonymous: user.is_anonymous,
				},
			})
			.catch(() => {});
		for (const identity of user.identities) {
			const existingAccounts = await ctx.internalAdapter.findAccounts(user.id);

			if (identity.provider === "email") {
				const hasCredential = existingAccounts.find(
					(account) => account.providerId === "credential",
				);
				if (!hasCredential) {
					await ctx.adapter
						.create({
							model: "account",
							data: {
								userId: user.id,
								providerId: "credential",
								accountId: user.id,
								password: user.encrypted_password,
								createdAt: new Date(user.created_at),
								updatedAt: new Date(user.updated_at),
							},
						})
						.catch(() => {});
				}
			}
			const supportedProviders = Object.keys(ctx.options.socialProviders || {})
			if (supportedProviders.includes(identity.provider)) {
				const hasAccount = existingAccounts.find(
					(account) => account.providerId === identity.provider,
				);
				if (!hasAccount) {
					await ctx.adapter.create({
						model: "account",
						data: {
							userId: user.id,
							providerId: identity.provider,
							accountId: identity.identity_data?.sub,
							createdAt: new Date(identity.created_at ?? user.created_at),
							updatedAt: new Date(identity.updated_at ?? user.updated_at),
						},
					});
				}
			}
		}
	}
};
migrateFromSupabase();

自定义迁移脚本(可选)

  • name: 迁移脚本将使用用户的电子邮件作为名称。如果您的数据库中存储了用户显示名称,您可能希望自定义它。
  • socialProviderList: 迁移脚本将使用您在认证配置中启用的社交提供程序。如果您有额外的社交提供程序尚未在认证配置中启用,您可能希望自定义它。
  • role: 如果您未使用 admin 插件,请移除 role
  • isAnonymous: 如果您未使用 anonymous 插件,请移除 isAnonymous
  • 更新引用 users 表的其他表以使用 id 字段。

运行迁移脚本

运行迁移脚本来将用户和账户从 Supabase 迁移到 Better Auth。

Terminal
bun migration.ts # 或者使用 node、ts-node 等。

更新您的代码

将您的代码库从 Supabase 认证调用更新为 Better Auth API。

以下是 Supabase 认证 API 调用及其 Better Auth 对应项的列表。

  • supabase.auth.signUp -> authClient.signUp.email
  • supabase.auth.signInWithPassword -> authClient.signIn.email
  • supabase.auth.signInWithOAuth -> authClient.signIn.social
  • supabase.auth.signInAnonymously -> authClient.signIn.anonymous
  • supabase.auth.signOut -> authClient.signOut
  • supabase.auth.getSession -> authClient.getSession - 您也可以使用 authClient.useSession 获取响应式状态

了解更多:

  • 基本用法:了解如何使用认证客户端进行注册、登录和登出。
  • 电子邮件和密码:了解如何向您的项目添加电子邮件和密码认证。
  • 匿名:了解如何向您的项目添加匿名认证。
  • 管理员:了解如何向您的项目添加管理员认证。
  • 电子邮件 OTP:了解如何向您的项目添加电子邮件 OTP 认证。
  • 钩子:了解如何使用钩子监听事件。
  • Next.js:了解如何在 Next.js 项目中使用认证客户端。

中间件

要使用中间件保护路由,请参考 Next.js 中间件指南 或您框架的文档。

结束语

恭喜!您已成功从 Supabase Auth 迁移到 Better Auth。

Better Auth 提供了更大的灵活性和更多功能——请务必探索 文档 以充分发挥其潜力。

On this page