10 min read

How Google-Like SSO Works — A Technical Deep Dive

A comprehensive breakdown of Single Sign-On architecture — from OAuth 2.0 and OpenID Connect token flows to session cookies, PKCE, token introspection, and the security engineering behind Google's identity platform.

What is SSO and Why Does it Exist?

Single Sign-On (SSO) is an authentication scheme that allows a user to log in once and gain access to multiple independent applications without re-entering credentials. When you click "Sign in with Google" on a third-party app, you are not giving that app your password. Instead, Google acts as a trusted Identity Provider (IdP), and the third-party app is a Service Provider (SP) or Relying Party (RP).

The Core Problem SSO Solves

  • Credential Fatigue: Users managing dozens of username/password pairs is a security disaster. Password reuse leads to credential stuffing attacks.
  • Security Centralization: Instead of every app storing password hashes (and potentially losing them in a breach), credential storage is centralized at the IdP.
  • Developer Simplicity: Third-party apps don't need to build their own authentication systems. They delegate identity to a trusted, battle-tested provider.
  • Unified Session Management: Log out once, log out everywhere. Revoke access centrally.

Key Participants in Every SSO Flow

  • Resource Owner: The user (you).
  • Identity Provider (IdP): Google, Microsoft, Okta — the entity that stores credentials and issues tokens.
  • Relying Party / Client Application: The app you want to sign into (e.g., Notion, Figma).
  • Resource Server: An API or backend that the client app calls on behalf of the user. It validates tokens.
  • Authorization Server: Often the same as the IdP; issues access tokens, refresh tokens, and ID tokens.

A Brief History: From SAML to Modern Auth

SSO isn't new. Back in 2005, enterprises adopted SAML 2.0 — an XML-based protocol built for corporate intranets, desktop browsers, and IT-managed applications. It worked, but it was designed before mobile apps, REST APIs, or SPAs existed. SAML assertions are verbose XML documents, the flow is browser-only, and integrating it as a developer is notoriously painful.

In 2012, the IETF published OAuth 2.0 (RFC 6749) — lightweight, JSON-based, and built for the modern web. The OpenID Foundation then layered OpenID Connect (OIDC) on top to add identity. Today, every major platform — Google, Apple, Microsoft, GitHub — uses OIDC for consumer auth. SAML lives on only in legacy enterprise integrations. For anything new, OAuth 2.0 + OIDC is the standard.

The Protocol Stack: OAuth 2.0 vs. OpenID Connect vs. SAML

These three are not competitors — they form a layered stack. OAuth 2.0 answers "what can this app access?", OIDC adds "who is the user?", and SAML is the older XML-based approach that tried to do both.


graph LR
    OAuth["OAuth 2.0\n(Authorization)"] -->|"adds identity"| OIDC["OpenID Connect\n(Authentication)"]
    SAML["SAML 2.0\n(Legacy Enterprise)"] -..->|"older alternative"| OIDC
      
  • OAuth 2.0 — Issues short-lived Access Tokens that grant scoped access to APIs. Not an auth protocol — it doesn't tell you who the user is.
  • OpenID Connect (OIDC) — Sits on top of OAuth 2.0 and adds an ID Token (a signed JWT with user claims) and a /userinfo endpoint. "Sign in with Google" is OIDC.
  • SAML 2.0 — XML assertions over browser redirects. Still alive in enterprise (Workday, Salesforce), but use OIDC for anything new.
Dimension SAML 2.0 OAuth 2.0 + OIDC
Format XML JSON / JWT
Mobile Support Poor (browser-only) First-class (PKCE)
API Authorization Not designed for it Core use case
Dev Experience Complex, hard to debug Simple, easy to inspect
Used Today Legacy Enterprise SaaS Consumer Apps, APIs, Modern SaaS

The OIDC Authorization Code Flow — Step by Step

The Authorization Code Flow is the gold standard for server-side web applications. It is the flow Google uses for "Sign in with Google." Understanding this is understanding how SSO actually works.


sequenceDiagram
    participant User as Resource Owner (User)
    participant App as Relying Party (e.g., Notion)
    participant Google as Google Auth Server (IdP)
    participant GAPI as Google UserInfo API

    User->>App: Clicks "Sign in with Google"
    App->>Google: 302 Redirect: /authorize?client_id=...&response_type=code&scope=openid email&redirect_uri=...&state=xyz&code_challenge=...
    Google->>User: Show Google Login Page
    User->>Google: Enter Credentials / Select Account
    Google->>App: 302 Redirect to redirect_uri?code=AUTH_CODE&state=xyz
    App->>Google: POST /token (code=AUTH_CODE, client_secret, code_verifier)
    Google->>App: {access_token, id_token (JWT), refresh_token, expires_in}
    App->>GAPI: GET /userinfo (Authorization: Bearer access_token)
    GAPI->>App: {sub, email, name, picture}
    App->>User: User is now logged in!
      

Phase 1 — The Authorization Request

The app redirects the browser to Google's Authorization Endpoint with a few key parameters:

  • response_type=code: Tells Google to return a short-lived authorization code, not the token directly — keeps tokens out of the browser URL and history.
  • scope=openid email profile: Declares what the app needs. openid triggers OIDC and unlocks the ID Token; email and profile request user data.
  • state: A random nonce generated by the app. Google echoes it back on redirect — the app verifies it to prevent CSRF attacks.

Phase 2 — The Token Exchange

After the user authenticates, Google sends a short-lived authorization code (~60 seconds TTL) to the redirect_uri. The app's backend server then makes a server-to-server POST request directly to Google's Token Endpoint — never via the browser. This is the critical security boundary: the client_secret (which proves the app's identity) never touches the browser.

Google validates the code and returns three tokens:

  • Access Token: A short-lived (typically 1 hour) credential to call Google APIs on the user's behalf.
  • ID Token (JWT): A signed JSON Web Token containing user identity claims (sub, email, aud, iss, exp). This is what the app uses to establish a user session.
  • Refresh Token: A long-lived token used to obtain new Access Tokens when the current one expires, without requiring the user to log in again.

The ID Token: A JWT

The ID Token is the heart of OIDC. It is a JSON Web Token (JWT), which is a self-contained, signed data structure that doesn't require a network round-trip to validate. A JWT is three Base64URL-encoded strings joined by dots:


flowchart LR
    A["Header\n(Algorithm & Key ID)"] --- B["."]
    B --- C["Payload\n(Claims / User Data)"]
    C --- D["."]
    D --- E["Signature\n(RSA-256 / EC-256)"]
      

The Validation Challenge

Once your app receives an ID Token, it faces a fundamental problem: how do you trust it? The token came over the network — it could be expired, tampered with, issued by a different identity provider entirely, or intended for a different app. Calling Google on every request to ask "is this token still valid?" would add a network round-trip to every single API call — a latency and availability nightmare at scale.

Validating an ID Token Without a Network Call

Because the ID Token is signed by Google using an RSA private key, your app can verify it locally using Google's public keys, fetched once from Google's JWKS (JSON Web Key Set) endpoint: https://www.googleapis.com/oauth2/v3/certs. Cache these keys (Google rotates them ~weekly). This means token validation is a pure CPU operation — no network round-trip to Google required on every API call.

PKCE: Protecting the Authorization Code

PKCE (Proof Key for Code Exchange, pronounced "pixie") is a security extension that prevents a malicious app on the same device from intercepting the authorization code redirect. It is mandatory for SPAs and mobile apps.


sequenceDiagram
    participant App
    participant Google

    App->>App: 1. Generate random code_verifier (43-128 chars)
    App->>App: 2. code_challenge = BASE64URL(SHA256(code_verifier))
    App->>Google: 3. /authorize?code_challenge=X9f2...&code_challenge_method=S256
    Google->>App: 4. Returns auth code
    App->>Google: 5. POST /token (code=..., code_verifier=original_secret)
    Google->>Google: 6. Verify: SHA256(code_verifier) == stored code_challenge?
    Google->>App: 7. Issues tokens only if match
      

A malicious app that intercepts the code in step 4 can't exchange it for tokens — it never saw the code_verifier.

Session Architecture: What Happens After Token Exchange?

After OIDC completes, the app creates its own server-side session. Tokens are never stored in the browser — localStorage is readable by any JavaScript and is an XSS vulnerability. Instead:


sequenceDiagram
    participant Browser
    participant Server
    participant Redis

    Browser->>Server: POST callback with auth code
    Server->>Server: Validate ID Token sig, iss, aud, exp
    Server->>Redis: Store session user_id, email, refresh_token
    Server->>Browser: Set HttpOnly Secure SameSite cookie with session_id
    Note over Browser: Only the opaque cookie is stored, no tokens
    Browser->>Server: GET /dashboard with cookie
    Server->>Redis: Lookup session_id, return user context
    Server->>Browser: Return response
      

The cookie flags are critical: HttpOnly blocks JavaScript from reading it (stops XSS theft), Secure ensures HTTPS-only transmission, and SameSite=Lax blocks the cookie on cross-site requests (stops CSRF).

Token Refresh

Access Tokens expire in ~1 hour. The server silently refreshes them using the stored Refresh Token — invisible to the user:


sequenceDiagram
    participant App
    participant Google

    App->>App: Access Token is expired
    App->>Google: POST /token (grant_type=refresh_token, refresh_token=..., client_id, client_secret)
    Google->>App: New access_token + new id_token (refresh_token may rotate)
    App->>App: Update stored tokens, continue request
      

Google's Infrastructure: How It Scales

Google processes billions of SSO authentications per day across Gmail, YouTube, Google Workspace, and millions of third-party apps. The architecture behind accounts.google.com is a masterclass in distributed systems engineering.


flowchart TD
    Client[Browser / Mobile] --> GLB[Global Load Balancer\nAnycast IP]
    GLB --> FE[Frontend Auth Servers\nMultiple Regions]
    FE --> AuthLogic[Authentication Logic\nMFA, Risk Engine, Session Lookup]
    AuthLogic --> SpannerDB[(Google Spanner\nGlobal User DB)]
    AuthLogic --> Bigtable[(Bigtable\nSession Store)]
    AuthLogic --> KeyStore[(Hardware Security Modules\nPrivate Keys)]
    AuthLogic --> RiskSignals[Risk Scoring Engine\nIP Reputation, Device Trust]
      

Spanner: The Global User Database

Google's user account database is built on Cloud Spanner, a globally distributed relational database offering external consistency (stronger than serializable) with automatic sharding. This means a user's account data is replicated across multiple continents with single-digit millisecond read latency globally. Credential validation happens within a single Spanner transaction.

Token Signing with Hardware Security Modules (HSMs)

Google's JWT signing private keys are stored in Hardware Security Modules (HSMs) — physical tamper-resistant cryptographic processors. The private key material never leaves the HSM. Instead, the signing operation (RSASSA-PKCS1-v1_5 or ES256) is performed inside the hardware. Even Google's own engineers cannot extract the raw key material. Keys are rotated automatically, and the corresponding public keys are published to the JWKS endpoint.

Risk-Based Authentication

Google doesn't just validate passwords. Every login attempt is scored by a risk engine that considers:

  • IP address reputation and geolocation (new country login?)
  • Device fingerprint (known device vs. new device)
  • User behavioral signals (login time patterns)
  • Velocity signals (too many logins in short time?)
  • Password breach detection (Is this credential in a known leaked dataset?)

If the risk score exceeds a threshold, Google requires an additional factor — a 2FA prompt, a security key challenge, or an email verification — before issuing tokens.

Security Attack Vectors and Mitigations

1. Open Redirect Attack

Attack: A malicious site constructs a login URL like: https://accounts.google.com/o/oauth2/auth?...&redirect_uri=https://evil.com, attempting to have Google redirect the auth code to the attacker's server.

Mitigation: Google strictly validates that the redirect_uri in the request exactly matches a pre-registered URI. Any mismatch causes an immediate hard error — no redirect is issued.

2. CSRF on the Redirect

Attack: An attacker initiates an OAuth flow with their own Google account and tricks a victim into completing the flow, linking the attacker's Google account to the victim's app account.

Mitigation: The state parameter. The app generates a random nonce before the redirect, stores it in the session, and verifies it matches on callback. The attacker cannot forge a state they don't control.

3. Authorization Code Interception

Attack: On mobile, a malicious app registered with the same custom URL scheme (myapp://callback) intercepts the authorization code redirect.

Mitigation: PKCE. The code is useless without the code_verifier that only the legitimate app holds.

4. JWT Algorithm Confusion Attack (alg=none)

Attack: An attacker crafts a JWT with alg: "none" and no signature, then presents it as a valid ID Token.

Mitigation: Always explicitly specify expected algorithms (e.g., RS256, ES256) in your JWT validation library. Never allow alg: "none". Use a well-maintained library (e.g., google-auth-library) rather than hand-rolling JWT validation.

5. Token Leakage via Referer Header

Attack: If an access token or code is included in a URL query parameter, it can leak via the HTTP Referer header to analytics or CDN logs.

Mitigation: The Authorization Code flow ensures tokens are never placed in the URL. The authorization code (which is in the URL) is short-lived (~60 seconds, single-use) and useless without the client_secret.

System Design: Building Your Own SSO System

What if you need to build internal SSO for a suite of microservices? Here is the reference architecture:


flowchart TD
    Apps["Internal Apps\n(HR, Finance, CRM)"] --> APIGW[API Gateway]
    APIGW --> AuthSvc[Auth Service\n/authorize /token /userinfo /introspect]
    AuthSvc --> UserDB[(User Store\nPostgres)]
    AuthSvc --> SessionStore[(Session Store\nRedis Cluster)]
    AuthSvc --> KeyVault[(Key Vault\nHSM / AWS KMS)]
    AuthSvc --> ExtIdP["External IdPs\nGoogle, GitHub (via OIDC)"]
    APIGW --> TokenCheck{Token Validation}
    TokenCheck -->|"JWT: Local verify\n(cached JWKS)"| FastPath[Fast Path ✅]
    TokenCheck -->|"Opaque: Introspect"| AuthSvc
      

The Auth Service Core Responsibilities

  • Session Issuance: After successful authentication (local or federated), issue a signed session token or opaque session ID stored in Redis with TTL.
  • Token Minting: Issue JWTs signed with a private key stored in KMS. Include jti (JWT ID) for revocation tracking.
  • JWKS Endpoint: Expose public keys at /.well-known/jwks.json so downstream services can validate tokens locally without calling the auth service.
  • Introspection Endpoint: For opaque tokens or real-time revocation checks.
  • Federation Gateway: Act as an OIDC Relying Party to external providers (Google, GitHub), normalizing their identity claims into your internal user model.

Caching Strategy for JWKS

Downstream services that validate JWTs locally need the signing public keys. Cache these aggressively with a stale-while-revalidate strategy: serve from cache immediately but refresh in the background. When you rotate keys, use a transitional period where both old and new keys are published. The JWT's kid (Key ID) header field tells validators which specific key to use, allowing zero-downtime key rotation.

Conclusion

Google-like SSO is a multi-layered system that combines the OAuth 2.0 delegation framework, the OpenID Connect identity layer, JWT cryptography, PKCE security extensions, hardware-backed key storage, risk-based authentication signals, and distributed session management. The elegance of the system lies in its security model: your credentials never touch third-party apps, tokens are cryptographically verifiable without network calls, and even if a short-lived access token leaks, the blast radius is strictly limited in time and scope. Understanding this architecture is foundational to building any modern, secure, user-facing platform at scale.


Related Guides:
← PreviousAd Click Event Aggregation: Real-Time Stream ArchitectureNext →Google Spanner: The Database That Redefined What's Possible