OAuth

Better Auth 如何处理 OAuth

Better Auth 内置支持 OAuth 2.0 和 OpenID Connect。这使得你可以通过流行的 OAuth 提供商(如 Google、Facebook、GitHub 等)进行用户身份验证。

如果你想使用的提供商没有直接支持,可以使用 Generic OAuth 插件 来实现自定义集成。

配置社交提供商

要启用社交提供商,需要为该提供商提供 clientIdclientSecret

下面是配置 Google 作为提供商的示例:

auth.ts
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 钩子的示例:

auth.ts
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,
                });
              }
            }
          }
        },
      },
    ],
  },
});

使用数据库钩子的示例:

auth.ts
 // 你也可以在数据库钩子中访问额外数据
  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 流的回调 URL
  • codeVerifier - OAuth 流的代码校验器
  • errorURL - OAuth 流的错误 URL
  • newUserURL - OAuth 流中新用户的 URL
  • link - OAuth 流的链接信息(邮箱和用户 ID)
  • requestSignUp - 是否请求注册
  • expiresAt - OAuth 状态的过期时间
  • [key: string]: 你在 OAuth 流中传入的任何额外数据

提供商选项

scope

访问请求的权限范围,例如 emailprofile

auth.ts
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}

auth.ts
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

一个自定义函数,将提供商返回的用户信息映射到数据库中的用户对象。

当你希望从提供商的资料中填充用户对象的额外字段,或改变默认的映射方式时非常有用。

auth.ts
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 提供商。对于内置提供商,需按需提供刷新令牌的自定义函数。

auth.ts
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

auth.ts
import { betterAuth } from "better-auth";

export const auth = betterAuth({
  // 其他配置...
  socialProviders: {
    tiktok: {
      clientKey: "YOUR_TIKTOK_CLIENT_KEY",
      clientSecret: "YOUR_TIKTOK_CLIENT_SECRET",
    },
  },
});

getUserInfo

自定义函数,用于从提供商获取用户信息,覆盖默认的用户信息获取流程。

auth.ts
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

禁用隐式注册新用户。启用后,登录时需要传入 requestSignUptrue 才能创建新用户。

auth.ts
import { betterAuth } from "better-auth";

export const auth = betterAuth({
  // 其他配置...
  socialProviders: {
    google: {
      clientId: "YOUR_GOOGLE_CLIENT_ID",
      clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
      disableImplicitSignUp: true,
    },
  },
});

prompt

授权码请求中使用的提示参数,控制认证流程行为。

auth.ts
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

授权码请求的响应模式,决定授权响应的返回方式。

auth.ts
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

移除提供商的默认权限范围。默认情况下,提供商会包含诸如 emailprofile 的权限。设置为 true 后,会移除默认权限,只使用你指定的权限。

auth.ts
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"], // 只使用此权限
    },
  },
});

其他提供商配置

每个提供商可能还有额外选项,请查看具体提供商文档了解更多细节。