OAuth

了解如何配置社交 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() // 包含用户会话令牌的请求头
});

验证 OAuth 用户信息

使用 user.validateUserInfo 在 Better Auth 创建用户、关联新账户或让现有用户重新登录之前拒绝 OAuth 身份。回调会接收映射后的 user,以及 source.oauth 中的提供商 id 和原始提供商资料。

当 OAuth 身份首次创建时(来自常规回调、ID token 登录、One Tap 或 OAuth Proxy 的 create-user),关联新账户时(link-account),以及现有 OAuth 用户每次登录时(sign-in),都会运行该回调。在 sign-in 操作中,user 携带的是提供商的最新邮箱,因此,如果用户的提供商邮箱后来变更为不允许的域名,域名检查会拒绝该用户。它适用于无状态配置,因为无论是否使用数据库,都会在相同的时间点运行。

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

export const auth = betterAuth({
  user: {
    validateUserInfo: ({ user, source }) => {
      if (source.oauth?.providerId !== "google") return;

      if (!user.email?.endsWith("@example.com")) {
        return {
          error: "email_not_allowed",
          errorDescription: "Use your example.com email to sign in",
        };
      }
    },
  },
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    },
  },
});

获取访问令牌

使用 getAccessToken 获取已关联账户的访问令牌。每个令牌或提供商资料请求都必须明确选择账户:将 Better Auth 账户记录的 id 作为 accountId 传入,或传入 useAccountCookie: true,从其签名 Cookie 中选择账户。providerId 永远不能作为账户选择器,不包含任何一种受支持选择器的请求均无效。

账户记录 ID 可从 listAccounts 获取。如果其访问令牌已过期,Better Auth 会在返回前刷新该令牌。

const { data: accounts, error } = await authClient.listAccounts();

if (error) {
  throw new Error(error.message);
}

const account = accounts?.find((account) => account.providerId === "google");

if (!account) {
  throw new Error("Google account is not linked");
}

const { accessToken } = await authClient.getAccessToken({
  accountId: account.id,
})

启用 account.storeAccountCookie 后,显式从签名 Cookie 中选择账户:

const { accessToken } = await authClient.getAccessToken({
  useAccountCookie: true,
});

在服务器端使用时,传入相同的账户记录 ID。如果不包含会话请求头,受信任的服务器调用也可以提供 userId

await auth.api.getAccessToken({
  body: {
    accountId: account.id,
  },
  headers: await headers(), // headers containing the user's session token
});

刷新访问令牌

当你需要立即刷新所选账户的访问令牌时,使用 refreshToken。它使用与 getAccessToken 相同的显式选择器约定。

const tokens = await authClient.refreshToken({
  accountId: account.id,
});

要改用签名账户 Cookie:

await auth.api.refreshToken({
  body: {
    useAccountCookie: true,
  },
  headers: await headers(),
});

获取提供商提供的账户信息

使用 accountInfo 获取已关联账户在提供商处的当前资料数据。传入 Better Auth 账户记录的 id,而不是提供商的 subject 标识符。

响应会将身份数据与资料数据分开。account 包含所选的 Better Auth 记录及其提供商密钥,user 包含可变的资料字段,data 包含原始提供商响应。

const info = await authClient.accountInfo({
  query: { accountId: account.id },
});

console.log(info.data?.account.accountId);
console.log(info.data?.user.email);

当应通过签名账户 Cookie 选择账户时:

const info = await authClient.accountInfo({
  query: { useAccountCookie: true },
});

服务器端用法:

await auth.api.accountInfo({
  query: {
    accountId: account.id,
  },
  headers: await headers(), // headers containing the user's session token
});

请求额外权限范围

有时你的应用可能需要用户在已注册后再授予额外的 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” 错误。

自定义授权 URL

要向提供商的授权端点传递额外查询参数,请在调用 signIn.sociallinkSocial 时传入 additionalParams。这些值会在框架写入 OAuth state、PKCE challenge 和 redirect_uri 后应用;保留键 stateclient_idredirect_uriresponse_typecode_challengecode_challenge_methodscope 会被拒绝并返回 400,因此调用方无法破坏回调关联。

await authClient.signIn.social({
  provider: "cognito",
  additionalParams: {
    identity_provider: "Google", // skip the Cognito hosted-UI picker
  },
});

await authClient.linkSocial({
  provider: "google",
  loginHint: "[email protected]",
  additionalParams: {
    access_type: "offline",
    prompt: "consent",
  },
});

提供商自身内置的查询参数(例如 Google 的 include_granted_scopes=true、Facebook 的 config_id,以及通过 identityProvider 设置时 Cognito 的 identity_provider)会与调用时的 additionalParams 合并;发生键冲突时,以调用时的值为准。

通过 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 回调路径。

使用 after 钩子的示例:

auth.ts
import { betterAuth } from "better-auth";
import { createAuthMiddleware, getOAuthState } from "better-auth/api";

export const auth = betterAuth({
  // 其他配置...
  hooks: {
    after: createAuthMiddleware(async (ctx) => {
      // Additional data is only available during OAuth callback
      if (ctx.path === "/callback/:id") {
        const additionalData = await getOAuthState<{
          referralCode?: string;
          source?: string;
        }>();

        if (additionalData) {
          // IMPORTANT: Validate and sanitize the data before using it
          // This data comes from the client and should not be trusted

          // Example: Validate and process referral code
          if (additionalData.referralCode) {
            const isValidFormat = /^[A-Z0-9]{6}$/.test(additionalData.referralCode);
            if (isValidFormat) {
              // Verify the referral code exists in your database
              const referral = await db.referrals.findByCode(additionalData.referralCode);
              if (referral) {
                // Safe to use the verified referral
                await db.referrals.incrementUsage(referral.id);
              }
            }
          }

          // Track analytics (low-risk usage)
          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 state 包含以下数据:

  • callbackURL - OAuth 流程的回调 URL
  • codeVerifier - OAuth 流程的代码验证器
  • errorURL - OAuth 流程的错误 URL
  • newUserURL - OAuth 流程的新用户 URL
  • link - OAuth 流程的关联信息(邮箱和用户 id)
  • requestSignUp - 是否请求注册 OAuth 流程
  • expiresAt - OAuth state 的过期时间
  • serverContext - 在服务器端设置且在重定向后仍保留的值(参见传递服务器信任的数据
  • [key: string] - 你传入的 additionalData。这些数据来源于客户端,因此应视为不受信任。

传递服务器信任的数据

additionalData 由客户端提供,因此必须在回调时验证后才能使用。当插件(或你自己的 before 钩子)需要在重定向过程中传递服务器派生的数据时,请使用 addOAuthServerContext。它会写入客户端无法填充的仅限服务器端的存储槽,这些值可在回调时通过 serverContext 读取。

auth.ts
import { betterAuth } from "better-auth";
import {
  addOAuthServerContext,
  createAuthMiddleware,
  getOAuthState,
} from "better-auth/api";

export const auth = betterAuth({
  hooks: {
    before: createAuthMiddleware(async (ctx) => {
      // Social and generic OAuth providers both sign in here.
      if (ctx.path === "/sign-in/social") {
        // Derived on the server, so it is safe to trust on the callback.
        await addOAuthServerContext({ tenantId: ctx.context.tenantId });
      }
    }),
    after: createAuthMiddleware(async (ctx) => {
      if (ctx.path === "/callback/:id") {
        const tenantId = (await getOAuthState())?.serverContext?.tenantId;
        // Safe to use without re-validation: the client could not set this.
      }
    }),
  },
});

处理不提供邮箱的提供商

Better Auth 目前要求每条用户记录都必须有电子邮件地址。大多数提供商会在使用 email scope 时返回邮箱,但有些提供商在合法情况下可能不会返回。当这种情况发生时,OAuth 流程会失败并报 error=email_not_found(而对于 Generic OAuth 插件则是 error=email_is_missing)。

下表总结了每个受影响的提供商:何时可能缺少 email、在 mapProfileToUser 中可作为回退的稳定标识符,以及对提供商 email_verified 信号的可信度。

提供商email 可能缺失的情况稳定的回退 IDemail_verified 的信任程度
Apple首次登录后的每次登录(Apple 仅在首次同意时发送 emailprofile.sub(每个 Apple Team 稳定)可靠;中继地址也会被标记为已验证
Discord仅使用手机号的账户;未授予 email scopeprofile.id(snowflake)可靠(专用的 verified 字段)
Facebook没有有效的邮箱,即使已授予 email 权限profile.id(应用范围)未知:Graph API 不提供逐邮箱验证标志
GitHub用户已将邮箱设为私密;GitHub App 缺少“Email addresses”权限profile.id(数字)可靠
LinkedIn成员没有已确认的邮箱;未授予 email scopeprofile.sub(每个应用成对)存在时可靠
Microsoft Entra ID托管用户没有 mail 属性,除非将 email 配置为可选声明profile.oid(在 profile.tid 内稳定)不可信:Microsoft 明确警告永远不要将其用于授权
Roblox默认 Roblox 资料流程不会返回邮箱;Better Auth 当前回退到 preferred_usernameprofile.sub(Roblox 用户 ID)对默认资料流程而言未知

使用 mapProfileToUser 合成占位邮箱

email 字段为 null 或不存在时,回退到提供商的稳定 ID:

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

export const auth = betterAuth({
  socialProviders: {
    discord: {
      clientId: process.env.DISCORD_CLIENT_ID!,
      clientSecret: process.env.DISCORD_CLIENT_SECRET!,
      mapProfileToUser: (profile) => ({
        email: profile.email ?? `${profile.id}@discord.placeholder.local`,
      }),
    },
    apple: {
      clientId: process.env.APPLE_CLIENT_ID!,
      clientSecret: process.env.APPLE_CLIENT_SECRET!,
      mapProfileToUser: (profile) => ({
        email: profile.email ?? `${profile.sub}@apple.placeholder.local`,
      }),
    },
    microsoft: {
      clientId: process.env.MICROSOFT_CLIENT_ID!,
      clientSecret: process.env.MICROSOFT_CLIENT_SECRET!,
      mapProfileToUser: (profile) => ({
        email: profile.email ?? `${profile.oid}@entra.placeholder.local`,
      }),
    },
  },
});

合成邮箱只是占位符,不是真实联系地址。发送邮件的插件(密码重置、魔法链接、邮箱验证、组织邀请)无法投递到这些地址。请使用你控制的域名,或使用保留后缀如 .invalid.local,以免误发到真实邮箱。

提供商特定说明

  • Apple:第一次看到邮箱时请将其持久化保存。Apple 不提供用户信息端点,因此如果你在首次登录时不保存它,之后就无法再获取。email_verifiedis_private_email 都会被序列化为 字符串"true" / "false"),而不是布尔值。
  • GitHub:默认会请求 user:email scope。私密邮箱在 /user 上仍会返回 null;主要已验证地址可在 /user/emails 获取。
  • Microsoft Entra ID:由于 email 可随租户变化且从不验证,请使用 profile.oid(不可变、在租户内稳定)作为身份锚点;仅将 email 视为资料属性。Microsoft 的声明验证指南明确警告,切勿将 emailpreferred_usernameunique_name 用于授权决策。
  • Facebook:由于没有逐邮箱验证标志,除非你自行执行验证挑战,否则应将每个 Facebook 邮箱都视为未验证。

Better Auth 会通过稳定的 (issuer, accountId) 键识别现有 OAuth 账户,但关联的 Better Auth 用户仍然需要电子邮件地址。不支持无邮箱用户的功能正在 #9124 中跟踪。

提供商选项

clientId

提供商签发的 OAuth 2.0 Client ID。

对于通过受众(audience)验证 ID token 的提供商(Google、Apple、Microsoft Entra、Facebook、Cognito),你可以传入一个数组,以接受为任意已配置客户端签发的 token。Better Auth 驱动授权码流程时会使用数组中的第一个条目;验证 ID token 的 aud 声明时会接受所有条目。这使得你可以使用单一后端配置实现跨平台登录(Web、iOS、Android),其中每个平台的原生 SDK 都会使用各自的 Client ID 签发 token。

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

export const auth = betterAuth({
  // 其他配置...
  socialProviders: {
    google: {
      clientId: [
        process.env.GOOGLE_WEB_CLIENT_ID as string,
        process.env.GOOGLE_IOS_CLIENT_ID as string,
        process.env.GOOGLE_ANDROID_CLIENT_ID as string,
      ],
      clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
    },
  },
});

所有 Client ID 必须位于同一个提供商项目中,这样用户同意才能共享。对于不通过受众验证 ID token 的提供商,只接受单个字符串。

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 的自定义函数。接收 token、可选的 nonce,以及请求端点上下文,因此你可以根据请求头或其他请求数据进行分支处理。

提供 verifyIdToken替换提供商内置的验证逻辑(签名、签发者、受众和过期时间)。你的回调必须自行执行这些检查。来自客户端的请求头(例如 x-platform)由攻击者控制——只能用它们来选择要验证的受众(或其他声明),不能把它们当作身份凭证本身。

auth.ts
import { betterAuth } from "better-auth";
import { createRemoteJWKSet, jwtVerify } from "jose";

const appleJwks = createRemoteJWKSet(
  new URL("https://appleid.apple.com/auth/keys"),
);

export const auth = betterAuth({
  socialProviders: {
    apple: {
      clientId: "YOUR_APPLE_CLIENT_ID",
      clientSecret: "YOUR_APPLE_CLIENT_SECRET",
      verifyIdToken: async (token, nonce, ctx) => {
        // 从请求中选择受众,然后进行加密验证。
        const audience =
          ctx?.headers?.get("x-platform") === "ios"
            ? process.env.APPLE_APP_BUNDLE_IDENTIFIER!
            : process.env.APPLE_CLIENT_ID!;
        try {
          const { payload } = await jwtVerify(token, appleJwks, {
            issuer: "https://appleid.apple.com",
            audience,
            maxTokenAge: "1h",
          });
          if (nonce && payload.nonce !== nonce) {
            return false;
          }
          return true;
        } catch {
          return false;
        }
      },
    },
  },
});

overrideUserInfoOnSignIn

一个布尔值,决定登录时是否覆盖数据库中的用户信息。默认是 false,即登录时不覆盖用户信息。如果希望每次登录都更新用户信息,设置为 true

requireEmailVerification

要求在创建会话前验证此提供商的邮箱。默认为 false

当提供商报告邮箱未验证时,Better Auth 仍会创建或关联用户和账户,但不会签发会话。OAuth 回调会重定向并附带 ?error=email_not_verified,ID token 登录则返回带有 EMAIL_NOT_VERIFIED 错误代码的 403。系统会根据你的 emailVerification 设置发送(或重新发送)验证邮件:sendOnSignUp 负责新用户,sendOnSignIn 负责回访用户。配置 emailVerification.sendVerificationEmail 并保持启用 sendOnSignUp,以确保被阻止的用户始终能收到验证链接。

该限制会检查本地用户的验证状态,而不是每次请求中的提供商声明。已经通过其他方式(例如邮箱和密码)完成验证的用户,即使提供商之后报告邮箱未验证,也仍可继续访问。

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

export const auth = betterAuth({
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
      requireEmailVerification: true,
    },
  },
});

此选项需按提供商单独启用,并且独立于 emailAndPassword.requireEmailVerification:启用邮箱和密码验证不会限制社交登录。

仅为报告可信 email_verified 信号的提供商启用此选项。有些提供商始终将邮箱报告为未验证(或从不返回邮箱),在这些提供商上启用该选项会阻止所有登录。请参阅处理不提供邮箱的提供商

mapProfileToUser

使用 mapProfileToUser 更改默认的用户映射,或根据提供商个人资料填充其他用户字段。

Better Auth 会将该函数的返回值视为提供商输入,尽管该函数是在你的服务器上运行的。它会在 OAuth 注册、登录时覆盖个人资料,以及账户关联时同步个人资料的过程中,应用 user.additionalFields 中的输入规则。允许输入的映射字段会被解析并存储,而对于标记为 input: false 的字段,其映射值会被忽略。

资料映射无法重新定义提供商账户身份。内置提供商会根据其文档说明的不可变资料字段派生身份。当默认的 subid 字段不是正确的标识符时,Generic OAuth 提供商会使用 accountSubject

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,
        };
      },
    },
  },
});

user.additionalFields 选项中声明映射字段, 并允许这些字段作为输入。无状态身份验证配置同样适用此规则。

服务器拥有的字段与授权声明

将角色、封禁状态、内部标记和组织成员身份等安全敏感字段设置为 input: false。不要仅为了让 mapProfileToUser 持久化提供商声明就启用输入,因为同一设置也会允许通用注册和用户更新请求提供该字段。

inputreturned 控制不同的方向。例如,{ input: false, returned: true } 定义了一个可读取的服务器拥有字段。API 输入和 mapProfileToUser 无法提供该字段,但 Better Auth 会在响应中包含其存储的值。

如果提供商声明决定了谁可以登录,请在 Better Auth 完成 OAuth 登录之前执行该策略。不要等到登录完成后再检查,因为 Better Auth 可能已经签发了有效会话。提供商存在专用选项时,优先使用该选项,例如 Google 提供商用于限制 Google Workspace 域名的 hd 选项。对于调用 getUserInfo 的流程,可以通过自定义实现验证提供商响应,并在策略验证失败时返回 null。对于不调用 getUserInfo 的独立登录路径,也要配置等效的强制执行逻辑。

如果还需要存储经过验证的声明,请将字段保持为 input: false,并使用应用程序的数据库层写入该字段。仅当静态值适用于通过该身份验证配置创建的每个用户时,才使用 defaultValue;不要将其用于存储从提供商个人资料中派生的值。

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

其他提供商配置

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