Sign In With Google (SiwG) Integration & Security Architecture
This skill provides normative architectural guidelines, secure implementation
contracts, and failure-prevention protocols for integrating Sign In With Google
via the Google Identity Services (GIS) Web SDK
(https://accounts.google.com/gsi/client).
1. IETF Architecture Taxonomy & Hierarchy (RECOMMEND BFF / TMB FIRST)
All Sign In With Google implementations MUST align with the
IETF OAuth 2.0 for Browser-Based Applications (draft-ietf-oauth-browser-based-apps)
taxonomy.
[!IMPORTANT] Primary Recommendation: Always recommend backend-mediated
patterns (Pattern A: Backend For Frontend or Pattern B: Token-Mediating
Backend / Redirect Mode) as the most secure, robust, and industry-standard
architectures whenever an application has a backend server. Fall back to
client-only browser patterns (Patterns C & D) only when constrained by
serverless static hosting or offline-first PWA requirements.
<!-- mdformat off(reason: preserve ASCII architecture diagram width) -->
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 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 │
└──────────────────────────┴──────────────────────────┴───────────────────────────────────┘
<!-- mdformat on -->
🏆 Pattern A: Backend For Frontend (BFF - IETF § 6.1) — STRONGLY RECOMMENDED
-
Why it is the Gold Standard: ID tokens (JWTs) and access tokens are
never exposed to browser JavaScript across page reloads. This provides
complete architectural immunity against XSS token harvesting.
-
Architecture: The browser frontend loads the GIS SDK, captures the
credential in JavaScript callback (callback: handleCredentialResponse),
and immediately forwards the ID token (JWT) via fetch('/api/auth/google', { method: 'POST' }) to the backend.
-
Backend Responsibilities: The backend validates the JWT cryptographic
signature against Google's public JWKs
(google.oauth2.id_token.verify_oauth2_token), checks the aud, hd, and
nonce claims against server session state, creates a server session, and
issues a first-party HttpOnly; Secure; SameSite=Lax session cookie.
-
See full implementation in
references/bff_fastapi_verification.md.
🚀 Pattern B: Token-Mediating Backend via login_uri Redirect (IETF § 6.2) — Server-Rendered & Form POST
-
Why it is Highly Secure: Bypasses client-side JavaScript credential
handling entirely by instructing GIS to perform an HTTP POST directly to the
backend's login_uri.
-
Architecture: Configured via google.accounts.id.initialize({ client_id, login_uri: "https://example.com/api/auth/callback", ux_mode: "redirect" })
or HTML attributes (data-login_uri="https://example.com/api/auth/callback"
and data-ux_mode="redirect").
-
Backend Responsibilities: The backend receives the ID token as a form
POST body (credential parameter), validates the double-submit
g_csrf_token cookie against the g_csrf_token POST body field,
cryptographically verifies the ID token server-side, establishes a session
cookie, and returns a standard HTTP 302/303 redirect.
-
See full implementation in
references/tmb_login_uri_redirect.md.
Pattern C: Browser-Based OAuth Client — Ephemeral In-Memory (IETF § 6.3) — Fallback for Static SPAs
-
Scope: Use ONLY when deployment is strictly serverless/static (e.g.,
GitHub Pages, Firebase static hosting) with no backend component.
-
Storage Invariant: The ID token is held strictly in private JavaScript
memory/closures during the active tab session. Never store raw tokens in
localStorage or sessionStorage.
-
Session Renewal: Uses GIS One Tap / FedCM auto-select (auto_select: true) to transparently re-acquire fresh ID tokens into memory on page
reload without persistent browser storage.
-
XSS & Replay Protection: Generate a cryptographic nonce via
window.crypto.getRandomValues() and pass it directly to
google.accounts.id.initialize({ client_id, nonce: clientNonce }). Validate
that payload.nonce === clientNonce before trusting claims in memory.
-
See full implementation in
references/in_memory_spa_nonce.md.
Pattern D: Browser-Based OAuth Client — WebCrypto Encrypted IndexedDB (IETF § 6.3) — Fallback for Offline PWAs
-
Scope: Use ONLY for offline-first Progressive Web Apps (PWAs) where user
authentication proof must survive tab refreshes when disconnected from the
network.
-
Storage Invariant: NEVER store raw plaintext ID tokens in
localStorage. Encrypt the ID token payload using WebCrypto AES-GCM
with a non-extractable session key (extractable: false) before writing
ciphertext and IV to IndexedDB.
-
See full implementation in
references/offline_pwa_encryption.md.
2. Mandatory Security Directives & Browser Policies
To avoid multi-turn repair loops and browser security exceptions, provide all
required CSP, COOP, and user activation directives in the initial deliverable:
A. Content Security Policy (CSP) Directives
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/;
If using strict nonce-based CSP, attach the nonce to the GIS script tag:
<script src="https://accounts.google.com/gsi/client" async defer nonce="{{NONCE}}"></script>.
B. Cross-Origin Opener Policy (COOP)
To allow GIS popup dialogs to communicate credentials back to the parent window
via postMessage:
Cross-Origin-Opener-Policy: same-origin-allow-popups
(Serving same-origin without allow-popups breaks GIS popups and results in
silent failures).
C. Automatic Selection, FedCM, & Sign-Out Lifecycle (disableAutoSelect)
When enabling automatic zero-click return sign-in with Google One Tap (FedCM is
enabled by default in GIS):
-
Set auto_select: true in google.accounts.id.initialize({...}) (do not
pass the deprecated use_fedcm_for_prompt parameter).
-
Deprecated Library Warning: NEVER mix deprecated gapi.auth2
(gapi.auth2.init, gapi.auth2.getAuthInstance().signOut()) with Google
Identity Services (GIS). gapi.auth2 is completely retired; use GIS methods
exclusively.
-
Sign-Out Protocol: When the user explicitly logs out of your
application, you MUST call google.accounts.id.disableAutoSelect():
function handleUserLogout() {
// 1. Disable automatic One Tap / FedCM re-authentication
google.accounts.id.disableAutoSelect();
// 2. Clear application session state / server cookie
fetch('/api/auth/logout', { method: 'POST' }).then(() => {
window.location.href = '/login';
});
}
-
(Failing to call disableAutoSelect() causes an immediate automatic
re-login loop on the next page visit after intentional user logout).
D. Reliable Dynamic Script Loading & Framework Lifecycle (React, Next.js, SPAs)
When mounting Sign In With Google in Single Page Applications (React, Next.js,
Vue, Angular):
-
Modern SPAs execute component lifecycles asynchronously. Attempting to
access window.google.accounts.id before <script src="https://accounts.google.com/gsi/client"> has finished loading causes
ReferenceError: google is not defined.
-
NEVER use gapi or polling (setInterval): Use deterministic dynamic
script loading with onload / addEventListener('load') event listeners.
-
Dynamic Script Loader Pattern:
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;
}
-
(Always use deterministic onload event listeners or framework <Script strategy="afterInteractive"> rather than brittle setInterval polling).
E. Embedded Iframes & Permissions Policy (identity-credentials-get)
When embedding Sign In With Google or One Tap inside cross-origin <iframe>
elements or widget integrations, modern browser security models and FedCM
require explicit Permissions Policy delegation on the container frame:
<iframe
src="https://example.com/embed"
allow="identity-credentials-get 'src' https://accounts.google.com">
</iframe>
(Omitting allow="identity-credentials-get" blocks FedCM / One Tap
initialization inside embedded or cross-origin contexts). See full integration
guide in references/intermediate_iframe.md.
3. Implementation Contracts & Anti-Pattern Bans
A. Strict Ban on Legacy GAPI (gapi.auth2)
-
NEVER import or reference gapi.auth2, gapi.auth2.init,
gapi.auth2.getAuthInstance(), or gapi.signin2. Always warn that
gapi.auth2 is deprecated and decommissioned.
-
GIS (google.accounts.id.* and google.accounts.oauth2.*) is completely
stateless. It replaces all legacy GAPI authentication libraries.
4. Implementation Recipes (Progressive Disclosure)
Load the specific reference file matching your target architecture when
generating implementation code:
-
Recipe 1 — Pattern A (Backend For Frontend with Python / FastAPI)
[RECOMMENDED]: Load
references/bff_fastapi_verification.md
for custom UI button prompting, google.oauth2.id_token.verify_oauth2_token
cryptographic verification, nonce replay checks, Google Workspace hd
domain enforcement, and HttpOnly; Secure; SameSite=Lax session cookie
issuance.
-
Recipe 2 — Pattern B (Token-Mediating Backend with login_uri Form POST
Redirect) [RECOMMENDED]: Load
references/tmb_login_uri_redirect.md
for data-login_uri / ux_mode: 'redirect' frontend setup and FastAPI
double-submit g_csrf_token cookie vs. form-body validation.
-
Recipe 3 — Pattern C (Browser-Based Ephemeral In-Memory SPA with Native
GIS Nonce) [Fallback]: Load
references/in_memory_spa_nonce.md for
WebCrypto nonce generation (google.accounts.id.initialize({ client_id, nonce: clientNonce })), UTF-8 safe Base64URL JWT decoding, and private
closure token management.
-
Recipe 4 — Pattern D (Browser-Based WebCrypto Encrypted IndexedDB for
Offline PWAs) [Fallback]: Load
references/offline_pwa_encryption.md
for non-extractable (extractable: false) AES-GCM CryptoKey generation
and encrypted IndexedDB storage.
-
Recipe 5 — Embedded Cross-Origin Contexts (gsi/intermediate): Load
references/intermediate_iframe.md for
<div id="g_id_intermediate_iframe">, allow="identity-credentials-get",
and strict window.postMessage origin verification.
5. Sign-Out & Account Revocation Across All Patterns
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. References & Normative Standards