Google 2: the OAuth round trip #341

Closed
opened 2026-09-10 08:12:38 -05:00 by bermudalamb · 0 comments
Owner

Part of #332. Phase 2 of six. Depends on #340.

Two routes, and a customer who already has a linked Google identity can sign in. Creating new accounts is deliberately held back to the next phase, so this one is only about the protocol.

Scope

  • Authorization code flow with PKCE. The browser is redirected to Google, comes back with a code, and the server exchanges it over its own TLS connection. The customer's browser never sees a token. PKCE goes in even though this is a confidential client — it costs one hash and closes code interception outright.
  • State and nonce in one short-lived httpOnly cookie, alongside the PKCE verifier, ten minute expiry. State proves the callback belongs to the request this browser started; nonce proves the token was minted for this attempt. Clear the cookie whatever happens, so one attempt cannot be replayed.
  • Verify the id token by its claims, not by fetching keys. The token arrives on a direct TLS connection to Google's token endpoint, which is the case OpenID Connect explicitly allows skipping signature verification for. Every claim check is still mandatory: iss (accept both spellings), aud (exactly our client id), exp, nonce, and a non-empty sub.
  • Do not request offline access and do not store a refresh token. Google answers one question once.
  • Look the identity up on (provider, provider_sub), refuse a disabled account exactly as the password and passkey paths do, stamp last_used_at, and call the shared signIn — the same one, not a second implementation that agrees today.
  • Rate limit the begin route.

The trap

The state cookie must be SameSite=Lax, not Strict. The callback arrives as a top-level navigation from Google, which is cross-site. Strict withholds the cookie, the state check fails, and every sign-in is refused with an error that looks like tampering.

Done when

A hand-inserted identity row lets a real Google account sign in locally and land on the storefront with a working session; a tampered state is refused; a replayed callback is refused; and a disabled account is refused.

Local only — see #332 and the plan for why QA cannot host this yet.

Part of #332. Phase 2 of six. Depends on #340. Two routes, and a customer who already has a linked Google identity can sign in. Creating new accounts is deliberately held back to the next phase, so this one is only about the protocol. ## Scope - **Authorization code flow with PKCE.** The browser is redirected to Google, comes back with a code, and the server exchanges it over its own TLS connection. The customer's browser never sees a token. PKCE goes in even though this is a confidential client — it costs one hash and closes code interception outright. - **State and nonce in one short-lived `httpOnly` cookie**, alongside the PKCE verifier, ten minute expiry. State proves the callback belongs to the request this browser started; nonce proves the token was minted for this attempt. Clear the cookie whatever happens, so one attempt cannot be replayed. - **Verify the id token by its claims, not by fetching keys.** The token arrives on a direct TLS connection to Google's token endpoint, which is the case OpenID Connect explicitly allows skipping signature verification for. Every claim check is still mandatory: `iss` (accept both spellings), `aud` (exactly our client id), `exp`, `nonce`, and a non-empty `sub`. - **Do not request offline access and do not store a refresh token.** Google answers one question once. - **Look the identity up on `(provider, provider_sub)`**, refuse a disabled account exactly as the password and passkey paths do, stamp `last_used_at`, and call the shared `signIn` — the same one, not a second implementation that agrees today. - **Rate limit the begin route.** ## The trap The state cookie must be `SameSite=Lax`, not `Strict`. The callback arrives as a top-level navigation from Google, which is cross-site. `Strict` withholds the cookie, the state check fails, and every sign-in is refused with an error that looks like tampering. ## Done when A hand-inserted identity row lets a real Google account sign in locally and land on the storefront with a working session; a tampered state is refused; a replayed callback is refused; and a disabled account is refused. Local only — see #332 and the plan for why QA cannot host this yet.
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bermudalamb/redefined-designs#341