Google Sign-In has a reputation for being fiddly. It isn’t — it’s just OIDC, documented better than most standards. Here’s the entire walkthrough.
1. Register your OAuth client (5 minutes)
Go to console.cloud.google.com → APIs & Services → Credentials → Create Credentials → OAuth Client ID. Pick “Web application” and add:
- Authorized origins:
https://yourapp.com(andhttp://localhost:3000for dev) - Authorized redirect URIs:
https://yourapp.com/auth/google/callback(exact match; trailing slashes count)
Copy the Client ID and Client Secret somewhere safe.
Also configure the OAuth consent screen — internal if you’re in Workspace, external otherwise. External apps start in “testing” mode and need Google verification before going public, which takes a week or two for most scopes.
2. Send the user to Google (5 minutes)
Redirect to:
https://accounts.google.com/o/oauth2/v2/auth
?client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.com/auth/google/callback
&response_type=code
&scope=openid profile email
&state=RANDOM_CSRF_TOKEN
&code_challenge=PKCE_CHALLENGE
&code_challenge_method=S256
Store the state and the PKCE code verifier in the user’s session (cookie, not localStorage — we need them across the redirect).
3. Handle the callback (5 minutes)
On /auth/google/callback, verify state matches what you stored, then POST to the token endpoint:
POST https://oauth2.googleapis.com/token
Content-Type: application/x-www-form-urlencoded
code=AUTH_CODE_FROM_CALLBACK
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&redirect_uri=https://yourapp.com/auth/google/callback
&grant_type=authorization_code
&code_verifier=PKCE_VERIFIER
You get back an id_token (a JWT), an access_token, and optionally a refresh_token. Decode the id_token, verify its signature against Google’s JWKS, check iss and aud, and you have the user.
The hidden step: everyone else works the same
GitHub, Microsoft, GitLab, Apple — every OIDC provider uses this flow. Learn it once, substitute the URLs, and you’ve shipped five sign-in options in an afternoon. Or use a library or managed provider and skip the boilerplate entirely.