Sign in with Google

Google’s OpenID Connect implementation. One of the closest-to-spec implementations available; minimal surprises.

Discovery Document

https://accounts.google.com/.well-known/openid-configuration

Fetch this at startup (and cache with sane TTL) rather than hardcoding endpoints.

Key Endpoints

PurposeURL
Authorizationhttps://accounts.google.com/o/oauth2/v2/auth
Tokenhttps://oauth2.googleapis.com/token
UserInfohttps://openidconnect.googleapis.com/v1/userinfo
JWKS (public keys)https://www.googleapis.com/oauth2/v3/certs
Token revocationhttps://oauth2.googleapis.com/revoke

ID Token Claims

Always present: iss, sub, aud, exp, iat.

Conditional on scope / account type:

ClaimCondition
email, email_verifiedemail scope granted
name, given_name, family_name, picture, localeprofile scope granted
hdUser is a Google Workspace member; value is the workspace domain (use to gate workspace-only access)
nonceSent in your auth request
at_hashHash of access token (for hybrid flows)

Identifier Choice

Google’s documentation is explicit: never use email as a unique user identifier. Always use sub.

Emails can change (workspace rename, account migration). The sub claim is stable and unique per Google account.

Signing Algorithm

ID Tokens are signed with RS256. Pin this; do not accept other algorithms.

Refresh Tokens

Google issues a refresh token only when:

  • access_type=offline is included in the auth request, AND
  • prompt=consent is included on the first request, OR the user has not previously granted consent.

If you ask for offline access on every login, you may not get a refresh token after the first because Google won’t re-prompt for consent. Plan for this.

Hosted Domain (hd) for Workspace Apps

For “internal app — only members of acme.com may sign in”:

if claims.get("hd") != "acme.com":
    raise PermissionDenied("not an acme.com user")

Don’t rely on email.endswith("@acme.com") — Workspace can have alias domains and the hd claim is the canonical signal.

Common Implementation Path

flowchart LR
    A[Discover via .well-known] --> B[Build authorize URL with state, nonce, PKCE]
    B --> C[Redirect user]
    C --> D[Receive code at callback]
    D --> E[Validate state]
    E --> F[Exchange code → tokens]
    F --> G[Validate id_token: sig, iss, aud, exp, nonce]
    G --> H[Find/create user by sub]

Pitfalls

  • Wrong aud after credential rotation — if you regenerated client credentials, old sessions reference the old aud. Reject and re-auth.
  • Assuming email_verified is true — for some legacy / educational accounts it isn’t. Check explicitly.
  • Hardcoding endpoints — Google has rotated them historically.

See Also