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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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
httpOnlycookie, 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.iss(accept both spellings),aud(exactly our client id),exp,nonce, and a non-emptysub.(provider, provider_sub), refuse a disabled account exactly as the password and passkey paths do, stamplast_used_at, and call the sharedsignIn— the same one, not a second implementation that agrees today.The trap
The state cookie must be
SameSite=Lax, notStrict. The callback arrives as a top-level navigation from Google, which is cross-site.Strictwithholds 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.