mcpField
S
sign-in-with-google-web

sign-in-with-google-web

github.com/google开发21.6k927 次安装今天更新
代码仓库

关于 sign-in-with-google-web

sign-in-with-google-web 是指导使用 Google Identity Services Web SDK 安全集成 Sign In With Google 的 Agent 技能。内容涵盖后端中介与纯浏览器架构、安全策略、会话处理及 Web 应用实现模式。

主要功能

  1. 01比较 BFF、令牌中介和纯浏览器模式
  2. 02说明 ID 令牌验证与会话处理
  3. 03列出 CSP、COOP 和 iframe 策略
  4. 04指导在 SPA 中加载 GIS 脚本
  5. 05涵盖登出和自动登录行为

适用场景

  1. 01为 Web 应用选择身份验证架构
  2. 02实现服务器端 ID 令牌验证
  3. 03配置 GIS 所需的浏览器安全策略
  4. 04为静态 SPA 或离线 PWA 构建登录流程

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/google/skills --skill sign-in-with-google-web

安装后在 Claude Code 中输入/sign-in-with-google-web确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
bff_fastapi_verification.md
in_memory_spa_nonce.md
intermediate_iframe.md
offline_pwa_encryption.md
tmb_login_uri_redirect.md

详细介绍

使用 Google 登录(SiwG)集成与安全架构

本技能提供规范性的架构指南、安全实现契约和故障预防协议,用于通过 Google Identity Services(GIS)Web SDK (https://accounts.google.com/gsi/client)集成使用 Google 登录。

1. IETF 架构分类与层级(优先推荐 BFF / TMB)

所有使用 Google 登录的实现都 MUST 符合 IETF《基于浏览器的应用程序 OAuth 2.0》(draft-ietf-oauth-browser-based-apps) 中的分类法。

重要

首要建议: 只要应用有后端服务,就始终优先推荐后端中介模式(模式 A:前端后端(BFF)或模式 B:令牌中介后端 / 重定向模式),因为它们是最安全、最可靠且符合行业标准的架构。仅当受到无服务器静态托管或离线优先 PWA 需求的限制时,才退而采用仅客户端浏览器模式(模式 C 和 D)。

┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                          IETF ARCHITECTURE HIERARCHY & SELECTION                        │
├──────────────────────────┬──────────────────────────┬───────────────────────────────────┤
│ Pattern A (RECOMMENDED): │ Pattern B (RECOMMENDED): │ Patterns C & D (Fallbacks):       │
│ Backend For Frontend     │ Token-Mediating Backend  │ Browser-Based OAuth Client        │
│ (BFF - IETF § 6.1)       │ (TMB / Redirect § 6.2)   │ (JavaScript-Only - IETF § 6.3)    │
├──────────────────────────┼──────────────────────────┼───────────────────────────────────┤
│ 🏆 GOLD STANDARD         │ 🚀 NATIVE FORM REDIRECT  │ ⚡ STATIC SPA / 💾 OFFLINE PWA    │
│ • Full-stack SPA + API   │ • Server-rendered & apps │ • Pure client-side or offline PWAs│
│ • Client JS fetch to API │ • GIS HTTP POST login_uri│ • C: Ephemeral in-memory closure  │
│ • HttpOnly session cookie│ • Direct server redirect │ • D: WebCrypto Encrypted IndexedDB│
│ • Tokens never in browser│ • Tokens never in JS     │ • Strict WebCrypto nonces / keys  │
└──────────────────────────┴──────────────────────────┴───────────────────────────────────┘
🏆 模式 A:前端后端(BFF - IETF § 6.1)——强烈推荐
  • 为何这是黄金标准:页面重新加载期间,ID 令牌(JWT)和访问令牌绝不会暴露给浏览器 JavaScript。这使架构能够完全免受 XSS 令牌窃取攻击。

  • 架构:浏览器前端加载 GIS SDK,通过 JavaScript 回调(callback: handleCredentialResponse)捕获凭据,并立即通过 fetch('/api/auth/google', { method: 'POST' }) 将 ID 令牌(JWT)转发到后端。

  • 后端职责:后端依据 Google 的公共 JWK 验证 JWT 加密签名 (google.oauth2.id_token.verify_oauth2_token),根据服务器会话状态检查 aud、hd 和 nonce 声明,创建服务器会话,并签发第一方 HttpOnly; Secure; SameSite=Lax 会话 Cookie。

  • 完整实现请参阅 references/bff_fastapi_verification.md。

🚀 模式 B:通过 login_uri 重定向的令牌中介后端(IETF § 6.2)——服务端渲染与表单 POST
  • 为何安全性极高:通过指示 GIS 直接向后端的 login_uri 执行 HTTP POST,完全绕过客户端 JavaScript 对凭据的处理。

  • 架构:通过 google.accounts.id.initialize({ client_id, login_uri: "https://example.com/api/auth/callback", ux_mode: "redirect" }) 或 HTML 属性(data-login_uri="https://example.com/api/auth/callback" 和 data-ux_mode="redirect")进行配置。

  • 后端职责:后端接收表单 POST 请求正文中的 ID 令牌(credential 参数),验证双重提交的 g_csrf_token Cookie 与 POST 请求正文中的 g_csrf_token 字段相符,在服务端以加密方式验证 ID 令牌,建立会话 Cookie,并返回标准 HTTP 302/303 重定向。

  • 完整实现请参阅 references/tmb_login_uri_redirect.md。

模式 C:基于浏览器的 OAuth 客户端 — 临时内存存储(IETF § 6.3)— 静态 SPA 的回退方案
  • 适用范围:仅在部署严格限定为无服务器/静态部署(例如 GitHub Pages、Firebase 静态托管),且不包含后端组件时使用。

  • 存储原则:在当前标签页会话期间,ID 令牌严格保存在私有 JavaScript 内存/闭包中。切勿将原始令牌存储在 localStorage 或 sessionStorage 中。

  • 会话续期:使用 GIS One Tap / FedCM 自动选择(auto_select: true),在页面重新加载时透明地重新获取新的 ID 令牌并存入内存,不使用持久化浏览器存储。

  • XSS 和重放保护:通过 window.crypto.getRandomValues() 生成加密 nonce,并将其直接传递给 google.accounts.id.initialize({ client_id, nonce: clientNonce })。在信任内存中的声明之前,验证 payload.nonce === clientNonce。

  • 完整实现见 references/in_memory_spa_nonce.md。

模式 D:基于浏览器的 OAuth 客户端 — 使用 WebCrypto 加密的 IndexedDB(IETF § 6.3)— 离线 PWA 的回退方案
  • 适用范围:仅用于离线优先的渐进式 Web 应用(PWA),此类应用要求用户身份验证凭据在断网时仍能经受标签页刷新。

  • 存储原则:切勿将未加密的原始 ID 令牌存储在 localStorage 中。在将密文和 IV 写入 IndexedDB 之前,使用不可导出的会话密钥(extractable: false)通过 WebCrypto AES-GCM 加密 ID 令牌载荷。

  • 完整实现见 references/offline_pwa_encryption.md。


2. 强制安全指令和浏览器策略

为避免多轮修复循环和浏览器安全异常,请在初始交付内容中提供所有必需的 CSP、COOP 和用户激活指令:

A. 内容安全策略(CSP)指令
Content-Security-Policy:
  script-src 'self' https://accounts.google.com/gsi/client;
  frame-src https://accounts.google.com/gsi/;
  connect-src https://accounts.google.com/gsi/;

如果使用严格的基于 nonce 的 CSP,请将 nonce 附加到 GIS 脚本标签: <script src="https://accounts.google.com/gsi/client" async defer nonce="{{NONCE}}"></script>。

B. 跨源打开器策略(COOP)

要允许 GIS 弹出对话框通过 postMessage 将凭据传回父窗口:

Cross-Origin-Opener-Policy: same-origin-allow-popups

(使用 same-origin 提供服务而不设置 allow-popups,会导致 GIS 弹出窗口失效并出现静默失败)。

C. 自动选择、FedCM 和登出生命周期(disableAutoSelect)

启用 Google One Tap 自动零点击返回登录时(GIS 默认启用 FedCM):

  • 在 google.accounts.id.initialize({...}) 中设置 auto_select: true(不要传入已弃用的 use_fedcm_for_prompt 参数)。

  • 已弃用库警告:切勿将已弃用的 gapi.auth2(gapi.auth2.init、gapi.auth2.getAuthInstance().signOut())与 Google Identity Services(GIS)混用。gapi.auth2 已彻底退役;请仅使用 GIS 方法。

  • 登出协议:用户明确退出应用时,必须调用 google.accounts.id.disableAutoSelect():

    function handleUserLogout() {
      // 1. 禁用 One Tap / FedCM 自动重新验证
      google.accounts.id.disableAutoSelect();
    
      // 2. 清除应用会话状态 / 服务端 Cookie
      fetch('/api/auth/logout', { method: 'POST' }).then(() => {
        window.location.href = '/login';
      });
    }
    
  • (如果未调用 disableAutoSelect(),用户主动登出后下次访问页面时会立即陷入自动重新登录循环)。

D. 可靠的动态脚本加载与框架生命周期(React、Next.js、SPA)

在单页应用(React、Next.js、Vue、Angular)中挂载 Google 登录时:

  • 现代 SPA 会异步执行组件生命周期。在 <script src="https://accounts.google.com/gsi/client"> 加载完成前尝试访问 window.google.accounts.id,会导致 ReferenceError: google is not defined。

  • 绝不要使用 gapi 或轮询(setInterval):使用确定性的动态脚本加载,并通过 onload / addEventListener('load') 事件监听器处理。

  • 动态脚本加载器模式:

    import React, { useEffect, useState } from 'react';
    
    export function useGoogleIdentityScript() {
      const [isLoaded, setIsLoaded] = useState(false);
    
      useEffect(() => {
        if (window.google?.accounts?.id) {
          setIsLoaded(true);
          return;
        }
    
        const existingScript = document.querySelector(
          'script[src="https://accounts.google.com/gsi/client"]'
        );
        if (existingScript) {
          existingScript.addEventListener('load', () => setIsLoaded(true));
          return;
        }
    
        const script = document.createElement('script');
        script.src = 'https://accounts.google.com/gsi/client';
        script.async = true;
        script.defer = true;
        script.onload = () => setIsLoaded(true);
        document.head.appendChild(script);
      }, []);
    
      return isLoaded;
    }
    
  • (始终使用确定性的 onload 事件监听器或框架的 <Script strategy="afterInteractive">,而不是脆弱的 setInterval 轮询。)

E. 嵌入式 Iframe 与权限策略(identity-credentials-get)

在跨源 <iframe> 元素或小组件集成中嵌入 Google 登录或 One Tap 时,现代浏览器安全模型和 FedCM 要求在容器框架上显式委派权限策略:

<iframe
  src="https://example.com/embed"
  allow="identity-credentials-get 'src' https://accounts.google.com">
</iframe>

(省略 allow="identity-credentials-get" 会阻止 FedCM / One Tap 在嵌入式或跨源环境中初始化。) 完整集成指南请参阅 references/intermediate_iframe.md。


3. 实现约定与反模式禁令

A. 严格禁止使用旧版 GAPI(gapi.auth2)
  • 绝不要导入或引用 gapi.auth2、gapi.auth2.init、 gapi.auth2.getAuthInstance() 或 gapi.signin2。始终提醒 gapi.auth2 已弃用并停止服务。

  • GIS(google.accounts.id.* 和 google.accounts.oauth2.*)完全无状态,取代了所有旧版 GAPI 身份验证库。


4. 实现方案(渐进式披露)

生成实现代码时,请加载与目标架构匹配的特定参考文件:

  • 方案 1 — 模式 A(使用 Python / FastAPI 的前端后端模式 [推荐]):加载 references/bff_fastapi_verification.md, 了解自定义 UI 按钮提示、google.oauth2.id_token.verify_oauth2_token 加密验证、nonce 重放检查、Google Workspace hd 域名强制执行,以及 HttpOnly; Secure; SameSite=Lax 会话 Cookie 的签发。

  • 方案 2 — 模式 B(使用 login_uri 表单 POST 重定向的令牌中介后端 [推荐]):加载 references/tmb_login_uri_redirect.md, 了解 data-login_uri / ux_mode: 'redirect' 前端设置,以及 FastAPI 双重提交 g_csrf_token Cookie 与表单正文验证。

  • 方案 3 — 模式 C(基于浏览器的临时内存 SPA,使用原生 GIS Nonce [回退方案]):加载 references/in_memory_spa_nonce.md,了解 WebCrypto nonce 生成(google.accounts.id.initialize({ client_id, nonce: clientNonce }))、UTF-8 安全的 Base64URL JWT 解码,以及私有 闭包令牌管理。

  • 方案 4 — 模式 D(基于浏览器的 WebCrypto 加密 IndexedDB,适用于 离线 PWA [回退方案]):加载 references/offline_pwa_encryption.md, 了解不可提取(extractable: false)的 AES-GCM CryptoKey 生成 及加密的 IndexedDB 存储。

  • 方案 5 — 嵌入式跨源上下文(gsi/intermediate):加载 references/intermediate_iframe.md,了解 <div id="g_id_intermediate_iframe">、allow="identity-credentials-get", 以及严格的 window.postMessage 来源验证。


5. 所有模式下的登出与账号撤销

export function handleSignOut(userEmail) {
  // 1. Disable client-side One Tap auto-selection
  google.accounts.id.disableAutoSelect();

  // 2. Revoke OAuth grant if user disconnected account
  if (userEmail) {
    google.accounts.id.revoke(userEmail, () => {
      console.log('User grant revoked');
    });
  }

  // 3. Clear storage / session
  AuthManager.clear(); // Pattern C
  fetch('/api/auth/logout', { method: 'POST' }); // Pattern A
}

6. 参考资料与规范性标准

评论

支持 Markdown