skillZs
★ LIVE SKILL TAGS ★
>>> LIVE SKILLS INDEX <<<
* OPEN SOURCE *
NO LOGIN, NO TRACKING
※ REAL INSTALL DATA ※
← back to all skills
ssheleg/sheleg-dev156 installs

google-signin

Use when implementing, reviewing or debugging Google login / sign-in / sign-up on a website — wiring the GIS button or One Tap, verifying Google ID tokens on the server, linking Google to existing password accounts, or fixing "origin is not allowed for the given client ID". Covers GCP OAuth client setup, backend ID-token verification, three-way account linking with the pre- hijacking guard, login-CSRF defense, nonce/replay protection and a mandatory security checklist. Triggers - "sign in with google", "google login", "google sign-in button", "GIS", "google.accounts.id", "gsi/client", "verify google token", "one tap", "account linking", "login csrf", "g_csrf_token", "вход через Google", "кнопка входа Google", "связать аккаунты", "проверить токен Google". For the broader library surface use google-auth instead.

How do I install this agent skill?

npx skills add https://github.com/ssheleg/sheleg-dev --skill google-signin
view source ↗

Is this agent skill safe to install?

  • Gen Agent Trust Hubpass

    The skill provides comprehensive and secure guidance for implementing Google Sign-In (GIS). It emphasizes industry best practices such as backend token verification using official libraries, nonce-based replay protection, and CSRF defense mechanisms. No malicious patterns, obfuscation, or unauthorized data access were detected.

  • Socketpass

    No alerts

  • Snykpass

    Risk: LOW · No issues

What does this agent skill do?

Google Sign-In (GIS) — production web login

Condensed operating instructions. Full narrative guide with diagrams, token anatomy, troubleshooting table, and a copy-paste FastAPI + JS skeleton: references/full-guide.md — read it when implementing from scratch; its FastAPI + JS skeleton is the reference implementation this skill ships.

Pick the right flow first

  • Only need "who is this user?" → GIS ID-token flow (this skill): load https://accounts.google.com/gsi/client, receive one Google-signed JWT, verify server-side. No redirect URI, no client secret, no Google-token storage.
  • Need to call Google APIs (Gmail/Drive/Calendar) on the user's behalf → OAuth 2.0 authorization-code flow instead (redirect URI + client secret + refresh tokens). Never use the code flow just for login.

Core flow (ID-token)

  1. Frontend: fetch a server-issued nonce first (GET /api/auth/google/nonce — the server stores it with a TTL, bound to a pre-auth HttpOnly cookie), then google.accounts.id.initialize({ client_id, callback, nonce })
    • renderButton(). A client-generated nonce POSTed back proves nothing: the value would be read out of the very JWT it is meant to check. The GSI script loads async — retry rendering (e.g. 20 × 150 ms) instead of silently dropping the button.
  2. Callback receives { credential } — a Google-signed ID token (JWT). POST it alone to your backend (no nonce in the body). Never treat it as a session.
  3. Backend: verify with the official lib — Python google.oauth2.id_token.verify_oauth2_token(credential, transport, CLIENT_ID), Node google-auth-library verifyIdToken({ idToken, audience }). That checks signature (Google JWKS), aud, iss, exp. Never hand-decode and trust the payload.
  4. Additionally require email_verified == true and pop the expected nonce from the pre-auth session (one-time consume) and require the token nonce claim to equal it exactly — a missing claim, a missing expectation, an expired or an already-consumed nonce all reject. This is what makes a stolen token non-replayable: a replay from a new session meets a different expectation, a replay in the same session meets a consumed one.
  5. Find-or-create the user, then issue YOUR OWN session (app JWT) as an HttpOnly + Secure + SameSite=Strict cookie. Google's token is verified once, never stored, never logged.

Identity & account linking (three-way branch)

Key the user on sub (stable Google user ID; store as google_id) — never on email (emails change, sub doesn't). On each Google login:

  1. Record with this google_id exists → login (refresh name/picture).
  2. Email matches an existing account without google_id → do NOT auto-link on email_verified alone. email_verified is Google saying it verified the inbox ONCE, not that this person owns the LOCAL account, and for a third-party address Google is not even authoritative that they still own the inbox. Default: link only after a fresh re-auth of the existing local account (its password / existing factor), so the person proves they hold the account Google is being attached to.
    • Auto-link WITHOUT that re-auth is allowed only when Google is authoritative for the address — email_verified AND (the address is @gmail.com OR the token carries an hd Workspace-domain claim) — AND the existing account's own email ownership was proven. For any other (third-party) address, run an independent challenge (a link to that inbox) before linking; Google's one-time verification is not current proof.
    • If the existing account is a password account whose email was never verified (a possible pre-registration hijack), send the person into a safe account-recovery flow — never "sign in with the existing password", which may be the attacker's.
  3. No match → create the user. Mark email_verified=True only when Google is authoritative for the address (Gmail or hd); for a third-party address record it unverified and challenge the inbox before granting anything that trusts the email.

Login-CSRF (cover BOTH delivery flows)

The two delivery flows are separate, explicitly typed paths, chosen by Content-Type — not one handler that claims both. A skeleton that declares form support but only binds a JSON body does not actually run the form contract.

  • Form-POST flow (login_uri auto-POST, application/x-www-form-urlencoded): GIS double-submits g_csrf_token in the FORM BODY and the cookie — require BOTH present and equal, constant-time (hmac.compare_digest). Missing either side → reject. This token is the whole authorization for the form path.
  • JS-fetch flow (application/json): no g_csrf_token, so require a trusted same-origin signal — Sec-Fetch-Site: same-origin (browser-set, unforgeable), OR, only when that header is ABSENT, an exact Origin in an allowlist. A missing Sec-Fetch-Site is NOT assumed same-origin; cross-site and none are rejected; and with neither a trusted metadata value nor an allowed Origin the request fails closed.

The CSRF decision runs BEFORE the token is verified — no external call on a request that has not proven its origin. Without this, an attacker's page can force-POST the attacker's credential and silently log the victim into the attacker's account.

Setup (GCP)

  1. Cloud Console → APIs & Services → OAuth consent screen (External; publish it — in "Testing" only allowlisted users can sign in).
  2. Credentials → Create OAuth client ID → Web application → Authorized JavaScript origins = every origin the button renders on, incl. http://localhost:<port> for dev (scheme+host+port, no path, no trailing slash). Authorized redirect URIs: leave empty for GIS.
  3. Ship only GOOGLE_CLIENT_ID (public; env var; one source of truth — inject into HTML server-side, e.g. via a <meta> tag). The client secret is UNUSED in the GIS flow — never put it in the app.
  4. CSP if present: allow https://accounts.google.com in script-src, connect-src, frame-src.
  5. If GOOGLE_CLIENT_ID is empty → hide the button and return "not configured" server-side (honest degradation, no crash).

Security checklist (verify ALL before calling it done)

  • Signature verified via official lib against Google JWKS (no verify=False, no manual base64 decode-and-trust)
  • aud == your client ID; exp enforced
  • email_verified required
  • Nonce: fresh per render, checked server-side against the token claim
  • Pre-hijacking guard on email-based linking
  • Login-CSRF: g_csrf_token double-submit AND same-origin check for the JS flow
  • Own session cookie: HttpOnly + Secure + SameSite=Strict
  • /api/auth/* rate-limited; auth events logged WITHOUT the credential
  • Session revocation path exists (e.g. an auth_version/ver claim check)
  • Logout also calls google.accounts.id.disableAutoSelect()

Debugging quick table

SymptomFix
"The given origin is not allowed for the given client ID"Add the exact origin (port, www.) to Authorized JavaScript origins; propagation takes minutes
Button never rendersGSI async race (add retry loop), empty client ID, or CSP blocks accounts.google.com
"Token used too early/expired"Server clock skew → sync NTP
Audience mismatchFrontend/backend client IDs differ → single env source of truth
access_denied on consentConsent screen still in Testing → publish the app
Works locally, breaks in prodProd origin missing in GCP, or Secure cookie served over plain HTTP
One Tap missing / FedCM console warningsFedCM is mandatory since Aug 2025 and the opt-out is gone (checked 2026-08-06). Load gsi/client from Google and keep it current — a vendored or pinned copy is the real break. The rendered button flow is unaffected; code branching on the old isNotDisplayed() moment callbacks is not

Degradation

  • Not Claude Code (Cursor, Codex, the skills CLI, the API container): the pack's PreToolUse gate does not run — the rules here hold, the refusal does not.
  • No browser channel: the sign-in button and its redirect are verified by reading the code, not by watching it work. Record that as the weaker claim it is.
  • No console access: creating the OAuth client is a human step; state it once, do the rest, and say what is waiting on it.

Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.

<a href="https://skillzs.dev/skills/ssheleg/sheleg-dev/google-signin">View google-signin on skillZs</a>