One Tap

Better Auth 的 One Tap 插件

一键登录插件允许用户通过 Google 的 One Tap API 只需轻点一次即可登录。该插件为您提供一个简单的方式将 One Tap 集成到您的应用中,负责处理客户端和服务器端的逻辑。

安装

添加服务器端插件

将 One Tap 插件添加到您的认证配置中:

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

export const auth = betterAuth({
    plugins: [
        oneTap({
          clientId: process.env.GOOGLE_CLIENT_ID as string, 
        }),
    ]
});

添加客户端插件

添加客户端插件并指定用户登录后应重定向到的位置,以及是否需要额外验证(如双因素认证)。

import { createAuthClient } from "better-auth/client";
import { oneTapClient } from "better-auth/client/plugins"; 

export const authClient = createAuthClient({
  plugins: [
    oneTapClient({
      clientId: "YOUR_CLIENT_ID",
      // 可选的客户端配置:
      autoSelect: false,
      cancelOnTapOutside: true,
      context: "signin",
      additionalOptions: {
        // Google 初始化方法的任何额外选项
      },
      // 配置提示行为和指数退避:
      promptOptions: {
        baseDelay: 1000,   // 重试延迟基数(毫秒,默认:1000)
        maxAttempts: 5     // 触发 onPromptNotification 前的最大尝试次数(默认:5)
      }
    })
  ]
});

用法

提示模式(默认)

要显示 One Tap 弹窗,只需在您的认证客户端调用 oneTap 方法:

import { authClient } from "@/lib/auth-client";

await authClient.oneTap(); 

按钮模式

如果想展示“使用 Google 登录”按钮而不是自动弹出提示,可使用 button 选项:

// 在您的组件内
<div id="google-signin-button"></div>

// 使用按钮配置调用 oneTap
await authClient.oneTap({
  button: {
    container: "#google-signin-button", // CSS 选择器或 HTMLElement
    config: {
      theme: "outline",
      size: "large",
      type: "standard",
      text: "signin_with"
    }
  }
});

自定义重定向行为

默认情况下,登录成功后插件会硬跳转到 /。您可以按以下方法自定义该行为:

避免硬跳转

传递带有 onSuccess 回调的 fetchOptions,用于在不重新加载页面的情况下处理登录响应:

import { authClient } from "@/lib/auth-client";

await authClient.oneTap({
  fetchOptions: {
    onSuccess: () => {
      // 例如,使用路由器导航而不完整重载页面:
      router.push("/dashboard");
    }
  }
});

指定自定义回调 URL

如果登录后想进行硬跳转至不同页面,使用 callbackURL 选项:

import { authClient } from "@/lib/auth-client";

await authClient.oneTap({
  callbackURL: "/dashboard"
});

使用指数退避处理提示被关闭

如果提示框关闭时用户尚未登录,插件会根据配置的 promptOptions 采用指数退避策略重试显示 One Tap 提示。应用通过 google.accounts.id.cancel() 关闭的提示框不会重试。

跳过的提示不会重试。Google 不会说明提示被跳过的原因;用户关闭提示后,Google 会进入冷却期,重试无法缩短该冷却时间。

插件会向 Google 的 prompt() 传入监听器来跟踪这些状态,因此 Google Identity Services 会记录一条关于提示 UI 状态方法的 FedCM 迁移警告。只要传入任何监听器,Google 都会记录该警告,即使监听器没有调用这些方法。在 FedCM 模式下,插件只会调用 FedCM 支持的方法,因此可以忽略此警告。

提示被跳过,或达到最大尝试次数仍未成功登录时,可通过 onPromptNotification 回调接收通知,并渲染备用界面(例如传统的 Google 登录按钮),让用户手动重新开始流程:

import { authClient } from "@/lib/auth-client";

await authClient.oneTap({
  onPromptNotification: (notification) => {
    console.warn("提示已被关闭或跳过。请考虑显示替代的登录选项。", notification);
    // 在这里渲染您的替代 UI
  }
});

客户端选项

  • clientId:您的 Google One Tap API 的客户端 ID。
  • autoSelect:如果用户已登录,自动选择账户。默认为 false。
  • cancelOnTapOutside:当用户点击弹窗外部时取消 One Tap 弹窗。请注意,当 FedCM 处于活动状态时,此选项可能无效,因为浏览器会管理取消行为。默认为 true。
  • uxMode:Google One Tap 流程使用的模式。可以是 "popup" 或 "redirect"。默认为 "popup"。
  • context:应使用 One Tap API 的上下文。可以是 "signin"、"signup" 或 "use"。默认为 "signin"。
  • additionalOptions:额外的选项,按照 Google Identity Services 文档 传递给 Google 的初始化方法。
  • promptOptions:提示行为和指数退避的配置:
    • baseDelay:重试延迟基数(毫秒)。默认是 1000。
    • maxAttempts:触发 onPromptNotification 回调前的最大尝试次数。默认是 5。
    • fedCM:启用时,调用 navigator.credentials.preventSilentAccess() 以在登出时清除浏览器的 FedCM 凭据状态。FedCM 本身由 Google Identity Services 库管理,无法禁用。默认是 true。

按钮模式选项

使用按钮模式时,向 oneTap 方法传递 button 对象,包含以下属性:

  • container:渲染按钮的 HTML 元素或 CSS 选择器(必需)
  • config:按钮配置选项(可选):
    • type:按钮类型——"standard"(默认)或 "icon"(仅图标)
    • theme:按钮主题——"outline"(默认)、"filled_blue" 或 "filled_black"
    • size:按钮大小——"large"(默认)、"medium" 或 "small"
    • text:按钮文本——"signin_with"(默认)、"signup_with"、"continue_with" 或 "signin"
    • shape:按钮形状——"rectangular"(默认)、"pill"、"circle" 或 "square"
    • logo_alignment:Google Logo 对齐方式——"left"(默认)或 "center"(仅标准按钮)
    • width:最小按钮宽度(像素,最大 400)
    • locale:使用指定的语言代码显示按钮(例如 "zh_CN")

服务器端选项

  • disableSignUp:禁用注册选项,仅允许现有用户登录。默认为 false。
  • clientId:用于验证 ID 令牌受众的 Google 客户端 ID。除非配置了 socialProviders.google.clientId,否则为必需项。

限制为 Google Workspace 域(hd)

One Tap 回调会应用 socialProviders.google.hd 中配置的受托管域限制,与标准的 Google 登录流程一致: 当 hd 设置为某个域时,如果令牌的 hd 声明缺失或不匹配,则会被拒绝。将 hd 设为 "*" 可允许任意 Google Workspace 托管域,同时仍会拒绝没有 hd 声明的令牌。

该值从 socialProviders.google 中读取,因此只有在您配置了 Google 提供程序时,限制才会生效。如果您只使用 oneTap({ clientId }) 而不配置 socialProviders.google,则不会应用受托管域限制。若要将 One Tap 登录限制为某个 Workspace 域,请在 socialProviders.google 中配置 hd。

将 Google 资料映射到用户

配置 socialProviders.google 时,One Tap 会像标准 Google 登录一样,使用其 mapProfileToUser 或 getUserInfo 构建用户。你映射的额外字段(例如来自 given_name 的名字)只有在 user.additionalFields 中声明且未标记为 input: false 时才会保存。账户始终以经过验证的令牌 sub 为键。

启用 overrideUserInfoOnSignIn 后,已有用户每次通过 One Tap 登录时都会根据映射后的资料更新。

如果只配置 oneTap({ clientId }),而未配置 socialProviders.google,One Tap 会使用令牌中的 name、email、email_verified 和 picture 声明。

已授权的 JavaScript 来源

请确保为您的客户端 ID 配置已授权的 JavaScript 来源(例如 http://localhost:3000、https://example.com)。这是 Google One Tap API 的必需步骤,除非正确设置了来源,否则无法正常工作。