从 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 文件。

终端
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。

终端
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 来获取响应式状态

了解更多:

  • 基本用法:了解如何使用身份验证客户端进行注册、登录和登出。
  • 邮箱和密码:了解如何为项目添加邮箱和密码身份验证。
  • 匿名登录:了解如何为项目添加匿名身份验证。
  • 管理员:了解如何为项目添加管理员身份验证。
  • 邮箱一次性密码:了解如何为项目添加邮箱一次性密码身份验证。
  • 钩子:了解如何使用钩子监听事件。
  • Next.js:了解如何在 Next.js 项目中使用身份验证客户端。

中间件

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

总结

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

Better Auth 提供了更大的灵活性和更多功能——请务必探索文档以解锁其全部潜力。

On this page