OAuth
Better Auth 如何处理 OAuth
Better Auth 内置支持 OAuth 2.0 和 OpenID Connect。这使得你可以通过流行的 OAuth 提供商(如 Google、Facebook、GitHub 等)进行用户身份验证。
如果你想使用的提供商没有直接支持,可以使用 Generic OAuth 插件 来实现自定义集成。
配置社交提供商
要启用社交提供商,需要为该提供商提供 clientId 和 clientSecret。
下面是配置 Google 作为提供商的示例:
import { betterAuth } from "better-auth";
export const auth = betterAuth({
// 其它配置...
socialProviders: {
google: {
clientId: "YOUR_GOOGLE_CLIENT_ID",
clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
},
},
});用法
登录
要使用社交提供商登录,可以使用 signIn.social 函数(客户端)或 auth.api(服务器端)调用。
// 客户端用法
await authClient.signIn.social({
provider: "google", // 或其它提供商 id
})// 服务器端用法
await auth.api.signInSocial({
body: {
provider: "google", // 或其它提供商 id
},
});关联账户
要将账户关联到社交提供商,可以使用 linkAccount 函数(客户端)或 auth.api(服务器端)调用。
await authClient.linkSocial({
provider: "google", // 或其它提供商 id
})服务器端用法:
await auth.api.linkSocialAccount({
body: {
provider: "google", // 或其它提供商 id
},
headers: await headers() // 包含用户会话令牌的请求头
});获取访问令牌
要获取社交提供商的访问令牌,可以使用 getAccessToken 函数(客户端)或 auth.api(服务器端)调用。如果访问令牌过期,将自动刷新。
const { accessToken } = await authClient.getAccessToken({
providerId: "google", // 或其它提供商 id
accountId: "accountId", // 可选,指定某个账户的访问令牌
})服务器端用法:
await auth.api.getAccessToken({
body: {
providerId: "google", // 或其它提供商 id
accountId: "accountId", // 可选,指定某个账户的访问令牌
userId: "userId", // 可选,若未提供带身份认证的请求头
},
headers: await headers() // 包含用户会话令牌的请求头
});获取提供商提供的账户信息
要获取提供商特定的账户信息,可以使用 accountInfo 函数(客户端)或 auth.api(服务器端)调用。
const info = await authClient.accountInfo({
accountId: "accountId", // 传入提供商的账户 ID,提供商会自动识别
})服务器端用法:
await auth.api.accountInfo({
query: { accountId: "accountId" },
headers: await headers() // 包含用户会话令牌的请求头
});请求额外权限范围
有时你的应用可能需要用户在已注册后再授予额外的 OAuth 权限范围(例如访问 GitHub 仓库或 Google Drive)。用户可能不想一开始就授予全部权限,更倾向于先授予最小权限,然后根据需要追加权限。
你可以用相同的提供商调用 linkSocial 方法请求额外权限,这会触发新的 OAuth 流程,申请额外权限,同时保持现有的账户关联。
const requestAdditionalScopes = async () => {
await authClient.linkSocial({
provider: "google",
scopes: ["https://www.googleapis.com/auth/drive.file"],
});
};确保你使用的 Better Auth 版本在 1.2.7 及以上。早期版本(例如 1.2.2)在尝试为已有账户关联追加权限时可能会出现 “Social account already linked” 错误。
通过 OAuth 流传递额外数据
Better Auth 允许你在 OAuth 流程中传递额外数据,但不将其存储到数据库。这适用于跟踪推荐码、分析来源或其他应在认证时处理但无需持久保存的临时数据。
发起 OAuth 登录或关联账户时,传递额外数据:
// 客户端:带额外数据登录
await authClient.signIn.social({
provider: "google",
additionalData: {
referralCode: "ABC123",
source: "landing-page",
},
});
// 客户端:带额外数据关联账户
await authClient.linkSocial({
provider: "google",
additionalData: {
referralCode: "ABC123",
},
});
// 服务器端:带额外数据登录
await auth.api.signInSocial({
body: {
provider: "google",
additionalData: {
referralCode: "ABC123",
source: "admin-panel",
},
},
});在钩子中访问额外数据
额外数据可以通过 getOAuthState 在 OAuth 回调时钩子中访问。
这通常适用于 /callback/:id 路径以及通用 OAuth 插件的回调路径(/oauth2/callback/:providerId)。
使用 after 钩子的示例:
import { betterAuth } from "better-auth";
import { getOAuthState } from "better-auth/api";
export const auth = betterAuth({
// 其他配置...
hooks: {
after: [
{
matcher: () => true,
handler: async (ctx) => {
// 额外数据仅在 OAuth 回调时可用
if (ctx.path === "/callback/:id") {
const additionalData = await getOAuthState<{
referralCode?: string;
source?: string;
}>();
if (additionalData) {
// 重要:使用前应验证并清理数据
// 此数据来自客户端,不可完全信任
// 示例:验证并处理推荐码
if (additionalData.referralCode) {
const isValidFormat = /^[A-Z0-9]{6}$/.test(additionalData.referralCode);
if (isValidFormat) {
// 验证推荐码是否存在数据库中
const referral = await db.referrals.findByCode(additionalData.referralCode);
if (referral) {
// 可安全使用已验证的推荐码
await db.referrals.incrementUsage(referral.id);
}
}
}
// 跟踪分析(低风险使用)
if (additionalData.source) {
await analytics.track("oauth_signin", {
source: additionalData.source,
userId: ctx.context.session?.user.id,
});
}
}
}
},
},
],
},
});使用数据库钩子的示例:
// 你也可以在数据库钩子中访问额外数据
databaseHooks: {
user: {
create: {
before: async (user, ctx) => {
if (ctx.path === "/callback/:id") {
const additionalData = await getOAuthState<{ referredFrom?: string }>();
if (additionalData?.referredFrom) {
return {
data: {
referredFrom: additionalData.referredFrom,
},
};
}
}
},
},
},
},默认情况下,OAuth 状态包含以下数据:
callbackURL- OAuth 流的回调 URLcodeVerifier- OAuth 流的代码校验器errorURL- OAuth 流的错误 URLnewUserURL- OAuth 流中新用户的 URLlink- OAuth 流的链接信息(邮箱和用户 ID)requestSignUp- 是否请求注册expiresAt- OAuth 状态的过期时间[key: string]: 你在 OAuth 流中传入的任何额外数据
提供商选项
scope
访问请求的权限范围,例如 email 或 profile。
import { betterAuth } from "better-auth";
export const auth = betterAuth({
// 其他配置...
socialProviders: {
google: {
clientId: "YOUR_GOOGLE_CLIENT_ID",
clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
scope: ["email", "profile"],
},
},
});redirectURI
提供商的自定义重定向 URI。默认使用 /api/auth/callback/${providerName}。
import { betterAuth } from "better-auth";
export const auth = betterAuth({
// 其他配置...
socialProviders: {
google: {
clientId: "YOUR_GOOGLE_CLIENT_ID",
clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
redirectURI: "https://your-app.com/auth/callback",
},
},
});disableSignUp
禁用新用户注册。
disableIdTokenSignIn
禁用使用 ID token 登录。默认对某些提供商(如 Google 和 Apple)启用。
verifyIdToken
验证 ID token 的自定义函数。
overrideUserInfoOnSignIn
一个布尔值,决定登录时是否覆盖数据库中的用户信息。默认是 false,即登录时不覆盖用户信息。如果希望每次登录都更新用户信息,设置为 true。
mapProfileToUser
一个自定义函数,将提供商返回的用户信息映射到数据库中的用户对象。
当你希望从提供商的资料中填充用户对象的额外字段,或改变默认的映射方式时非常有用。
import { betterAuth } from "better-auth";
export const auth = betterAuth({
// 其他配置...
socialProviders: {
google: {
clientId: "YOUR_GOOGLE_CLIENT_ID",
clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
mapProfileToUser: (profile) => {
return {
firstName: profile.given_name,
lastName: profile.family_name,
};
},
},
},
});你可能想在 mapProfileToUser 中传入用户对象的额外字段,需先配置 user.additionalFields 选项。
(无状态认证设置同理)
refreshAccessToken
自定义刷新令牌的函数。此功能仅支持内置社交提供商(Google、Facebook、GitHub 等),暂不支持通过通用 OAuth 插件配置的自定义 OAuth 提供商。对于内置提供商,需按需提供刷新令牌的自定义函数。
import { betterAuth } from "better-auth";
export const auth = betterAuth({
// 其他配置...
socialProviders: {
google: {
clientId: "YOUR_GOOGLE_CLIENT_ID",
clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
refreshAccessToken: async (token) => {
return {
accessToken: "new-access-token",
refreshToken: "new-refresh-token",
};
},
},
},
});clientKey
你的应用客户端密钥。TikTok 社交提供商使用此项替代 clientId。
import { betterAuth } from "better-auth";
export const auth = betterAuth({
// 其他配置...
socialProviders: {
tiktok: {
clientKey: "YOUR_TIKTOK_CLIENT_KEY",
clientSecret: "YOUR_TIKTOK_CLIENT_SECRET",
},
},
});getUserInfo
自定义函数,用于从提供商获取用户信息,覆盖默认的用户信息获取流程。
import { betterAuth } from "better-auth";
export const auth = betterAuth({
// 其他配置...
socialProviders: {
google: {
clientId: "YOUR_GOOGLE_CLIENT_ID",
clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
getUserInfo: async (token) => {
// 自定义实现获取用户信息
const response = await fetch("https://www.googleapis.com/oauth2/v2/userinfo", {
headers: {
Authorization: `Bearer ${token.accessToken}`,
},
});
const profile = await response.json();
return {
user: {
id: profile.id,
name: profile.name,
email: profile.email,
image: profile.picture,
emailVerified: profile.verified_email,
},
data: profile,
};
},
},
},
});disableImplicitSignUp
禁用隐式注册新用户。启用后,登录时需要传入 requestSignUp 为 true 才能创建新用户。
import { betterAuth } from "better-auth";
export const auth = betterAuth({
// 其他配置...
socialProviders: {
google: {
clientId: "YOUR_GOOGLE_CLIENT_ID",
clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
disableImplicitSignUp: true,
},
},
});prompt
授权码请求中使用的提示参数,控制认证流程行为。
import { betterAuth } from "better-auth";
export const auth = betterAuth({
// 其他配置...
socialProviders: {
google: {
clientId: "YOUR_GOOGLE_CLIENT_ID",
clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
prompt: "select_account", // 或 "consent", "login", "none", "select_account+consent"
},
},
});responseMode
授权码请求的响应模式,决定授权响应的返回方式。
import { betterAuth } from "better-auth";
export const auth = betterAuth({
// 其他配置...
socialProviders: {
google: {
clientId: "YOUR_GOOGLE_CLIENT_ID",
clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
responseMode: "query", // 或 "form_post"
},
},
});disableDefaultScope
移除提供商的默认权限范围。默认情况下,提供商会包含诸如 email 和 profile 的权限。设置为 true 后,会移除默认权限,只使用你指定的权限。
import { betterAuth } from "better-auth";
export const auth = betterAuth({
// 其他配置...
socialProviders: {
google: {
clientId: "YOUR_GOOGLE_CLIENT_ID",
clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
disableDefaultScope: true,
scope: ["https://www.googleapis.com/auth/userinfo.email"], // 只使用此权限
},
},
});其他提供商配置
每个提供商可能还有额外选项,请查看具体提供商文档了解更多细节。