Warming up the neural circuits...
By the end of this chapter you will:
OAuth 2.0 is the protocol behind every "Sign in with Google/GitHub/Apple" . It's also behind authorization for Stripe, GitHub, and thousands of other services. Understanding OAuth deeply means you can implement social login securely and integrate with any third-party API.
You don't give the valet your house keys, your mailbox key, and your office badge. You give them ONLY the valet key — it starts the car and nothing else. OAuth works the same way. When an app asks "Can we access your Google Drive?", you're giving it a valet key (access token) with specific permissions (scopes) to your Google account. The app never sees your Google password. You can revoke the valet key at any time.
OAuth 2.0 is an authorization framework. It answers: "Can this app access my data?" OpenID Connect (OIDC) is an authentication layer built on top of OAuth 2.0. It answers: "Who is this user?"
Every "Sign in with Google" button uses OIDC. Every "Connect your GitHub account to our app" uses OAuth 2.0.
| OAuth 2.0 | OIDC | |
|---|---|---|
| Purpose | Authorization (access to resources) | Authentication (identity) |
| Returns | Access token | ID token () + access token |
| ID token? | No | Yes — contains user identity claims |
| Example | "Stripe can access your GitHub repos" | "Sign in to our app with Google" |
The Authorization Code flow is the only flow you should use for server-side web apps. It involves three parties: the User, your App, and the Authorization Server (Google/GitHub).
1. User clicks "Sign in with Google"
2. Your app redirects to Google's authorization endpoint:
https://accounts.google.com/o/oauth2/v2/auth?
client_id=YOUR_CLIENT_ID&
redirect_uri=https://yourapp.com/auth/callback&
response_type=code&
scope=openid%20email%20profile&
code_challenge=PKCE_CHALLENGE& ← PKCE
code_challenge_method=S256&
state=RANDOM_STATE ← CSRF protection
3. User authenticates with Google, approves scopes
4. Google redirects back to your app with an authorization code:
https://yourapp.com/auth/callback?code=AUTH_CODE&state=RANDOM_STATE
5. Your server exchanges the code for tokens (server-to-server):
POST https://oauth2.googleapis.com/token
Body: { code, client_id, client_secret, code_verifier=PKCE_VERIFIER, grant_type='authorization_code' }
6. Google returns: { access_token, id_token, refresh_token }
7. Your server validates the ID token, extracts user info, creates/updates user in DB
8. Your server creates a session or JWT for the user// Step 1: Redirect user to Google
const crypto = require('crypto');
function generatePKCE() {
const verifier = crypto.randomBytes(32).toString('base64url');
const challenge = crypto
PKCE (Proof Key for Code Exchange, pronounced "pixie") prevents authorization code interception attacks. Without PKCE:
1. Attacker's malicious app initiates OAuth flow → gets authorization code
2. Attacker intercepts the redirect to your app (via custom URL scheme on mobile, or
a malicious browser extension)
3. Attacker exchanges the stolen code for tokens → your app gets nothing
4. The user's account is now linked to the attacker's sessionPKCE works because the code verifier is known only to the original requester. Even if an attacker intercepts the authorization code, they can't exchange it for tokens without the code verifier.
The OAuth 2.1 draft makes PKCE mandatory for all authorization code flows. Even if you're using a server-side flow with a client secret, add PKCE. It's 10 lines of code and prevents an entire class of attacks. Auth0, Okta, Google, and GitHub all recommend (or require) PKCE.
Scopes define what the access token can do. Always request the minimum:
// ❌ Over-permissioned
scope: 'https://www.googleapis.com/auth/drive' // Full access to all Drive files
// ✅ Least privilege
scope: 'https://www.googleapis.com/auth/drive.file' // Only files created by this appFor social login (OIDC), you typically need only: openid email profile. Don't request drive, calendar, or other scopes unless your app genuinely needs them. Every additional scope is additional risk if tokens are compromised.
Notion uses OAuth 2.0 for its API integrations and social login. Key design decisions:
| Mistake | Why it's wrong | What to do instead |
|---|---|---|
Not validating the state parameter | CSRF: attacker can link their social account to the victim's app account | Generate a random state, store it (session/DB), validate on callback |
| Not using PKCE | Authorization code interception possible on mobile and desktop apps | Always use PKCE (S256). It's required in OAuth 2.1. |
| Requesting too many scopes | If a token is stolen, the attacker has excessive access | Request only the scopes you need: openid email profile for login |
| Storing OAuth secrets in frontend code | client_secret in = public | Server-side only. Use BFF or Next.js API routes for the token exchange |
| Not validating the ID token | Attacker could forge a token or use a token from a different issuer | Validate iss, aud, exp, nbf, signature. Use the provider's JWKS endpoint. |
| Hardcoding redirect URIs incorrectly | Mismatch between configured URI and actual redirect → OAuth flow breaks | Use exact match. Include trailing slash if configured. Test in staging. |
openid-client (Node.js), Passport.js strategies, or NextAuth.js. They handle PKCE, , token exchange, and ID token verification correctly.iss (issuer) claim. Ensures the ID token came from the expected provider (Google, GitHub), not a malicious one.aud (audience) claim. Ensures the ID token was issued for your specific client ID, not a different application.nonce parameter for replay protection. Include a random nonce in the auth request, validate it in the ID token response./auth/:provider route that works with Google, GitHub, and Apple using a unified interface. Each provider implements getAuthorizationUrl(), exchangeCode(), getUserInfo().What's the difference between OAuth 2.0 and OpenID Connect? OAuth 2.0 is for authorization (granting access to resources). OpenID Connect is a layer on top of OAuth 2.0 for authentication (verifying identity). OIDC adds an ID token (JWT) that contains user identity claims.
What is an OAuth scope? A permission string that specifies what access the client is requesting. Example: email profile grants access to the user's email and profile information. Scopes implement the principle of least privilege.
Why can't you put the OAuth client secret in frontend code? Anyone can view frontend source code. If the client secret is exposed, an attacker can impersonate your application and obtain tokens on behalf of your users.
Explain the OAuth Authorization Code flow with PKCE. (1) Client generates a code verifier and challenge (SHA-256 hashed verifier). (2) Client redirects user to authorization server with code challenge. (3) User authenticates and approves. (4) Authorization server redirects back with an authorization code. (5) Client exchanges the code + code verifier for tokens. (6) Authorization server validates the verifier against the challenge — rejects if mismatch. PKCE ensures that even if the authorization code is intercepted, the attacker cannot exchange it for tokens without the verifier.
What is the state parameter and why is it required? A random value generated by the client and validated on callback. It prevents CSRF attacks where an attacker initiates an OAuth flow and tricks the victim into completing it, linking the attacker's social account to the victim's app account. The state ties the callback to the original request.
How do you handle account linking when a user signs in with multiple OAuth providers? Match by verified email — if the email from Google matches the email from GitHub, link the accounts to the same user record. Store each provider's ID in a separate table (user_oauth_accounts with provider + provider_user_id). Always require email verification from the provider before trusting the email for linking.
OAuth 2.0 is the standard for delegated authorization. OIDC extends it for authentication ("Sign in with..."). The authorization code flow with PKCE is the only flow you should implement for server-side web apps. Always validate the state parameter (CSRF protection) and the ID token claims (iss, aud, exp). Request minimum scopes. Use established libraries (Passport, NextAuth, openid-client) rather than implementing the protocol from scratch.
state parameter (CSRF protection).iss, aud, exp on ID token. Request minimum scopes.state parameter for? CSRF protection — ties the OAuth callback to the original request.iss (issuer), aud (audience), exp (expiration).openid email profile — minimum needed for identity.