OAuth 2.0 & OpenID Connect

TL;DR

OAuth 2.0 lets users grant apps limited access to their data without sharing passwords. OpenID Connect (OIDC) adds an identity layer on top — it's how "Login with Google/GitHub" works. The authorization code flow with PKCE is the recommended approach for most apps.

Explain Like I'm 12

Imagine you're at a hotel and you want room service to clean your room. You DON'T give them your master key. Instead, you go to the front desk (authorization server), say "let housekeeping into my room for 1 hour" and they get a temporary key card (access token) that only works for your room, only for cleaning, and only for 1 hour. That's OAuth — you never shared your password, and the access is limited and temporary.

What Problem Does OAuth Solve?

Before OAuth, if a third-party app (like a calendar tool) needed your Gmail contacts, you'd give it your Google password. This was terrible — the app could read your email, change your password, or do anything as you. OAuth fixes this by letting you grant limited, revocable access without sharing credentials.

OAuth 2.0 authorization code flow showing User, App, Auth Server, and Resource Server interactions step by step
Info: OAuth 2.0 is an authorization protocol — it handles access delegation, not identity. OIDC adds the identity ("who is this user?") layer on top.

The Four Roles

RoleWhat It IsReal Example
Resource OwnerThe user who owns the dataYou (the Google account holder)
ClientThe app requesting accessA calendar app wanting your contacts
Authorization ServerIssues tokens after user consentaccounts.google.com
Resource ServerHosts the protected data/APIcontacts.google.com API

Authorization Code Flow (with PKCE)

This is the recommended flow for web apps, mobile apps, and SPAs. PKCE (Proof Key for Code Exchange) prevents authorization code interception attacks.

  1. App generates a code_verifier (random string) and its SHA-256 hash (code_challenge)
  2. User is redirected to the auth server with the code_challenge
  3. User logs in and consents to the requested scopes
  4. Auth server redirects back with an authorization code
  5. App exchanges the code + code_verifier for tokens
  6. Auth server verifies the code_verifier matches and returns access + refresh tokens
// Step 1: Generate PKCE values
function generateCodeVerifier() {
  const array = new Uint8Array(32);
  crypto.getRandomValues(array);
  return base64URLEncode(array);
}

async function generateCodeChallenge(verifier) {
  const hash = await crypto.subtle.digest(
    'SHA-256',
    new TextEncoder().encode(verifier)
  );
  return base64URLEncode(new Uint8Array(hash));
}

// Step 2: Build the authorization URL
const authUrl = new URL('https://accounts.google.com/o/oauth2/v2/auth');
authUrl.searchParams.set('client_id', CLIENT_ID);
authUrl.searchParams.set('redirect_uri', 'https://myapp.com/callback');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('scope', 'openid email profile');
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');

window.location.href = authUrl.toString();
// Step 5: Exchange code for tokens (server-side)
const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: authorizationCode,
    redirect_uri: 'https://myapp.com/callback',
    client_id: CLIENT_ID,
    code_verifier: storedCodeVerifier
  })
});

const { access_token, refresh_token, id_token } = await tokenResponse.json();
Warning: Never use the Implicit flow (response_type=token) — it exposes tokens in the URL fragment. Always use Authorization Code + PKCE, even for SPAs.

OAuth 2.0 Grant Types Compared

Grant TypeUse CaseSecurity
Authorization Code + PKCEWeb apps, mobile, SPAsBest — code exchange with proof key
Client CredentialsService-to-service (no user)Good — only for trusted backends
Device CodeSmart TVs, CLI tools (no browser)Good — user authorizes on another device
ImplicitLegacy SPAsDeprecated — tokens exposed in URL
Password (ROPC)First-party appsDeprecated — shares credentials with app
Tip: The Client Credentials flow is for machine-to-machine communication where there's no human user. Think: a cron job calling an API.

Scopes: Limiting Access

Scopes define what the token can access. The user sees them on the consent screen and can approve or deny.

# Google scopes example
scope=openid email profile                    # Basic identity
scope=https://www.googleapis.com/auth/drive.readonly  # Read Google Drive

# GitHub scopes example
scope=read:user repo                          # Read profile + access repos
scope=read:org                                # Read org membership
Info: Always request the minimum scopes needed. Users are more likely to grant consent, and if the token is compromised, the blast radius is limited.

OpenID Connect (OIDC)

OAuth 2.0 handles authorization (what can the app do?) but not authentication (who is the user?). OpenID Connect adds an ID token — a JWT containing user identity claims.

// Decoded ID token (JWT payload)
{
  "iss": "https://accounts.google.com",
  "sub": "110169484474386276334",
  "email": "[email protected]",
  "name": "Jane Doe",
  "picture": "https://lh3.googleusercontent.com/a/photo.jpg",
  "aud": "your-client-id.apps.googleusercontent.com",
  "iat": 1743638400,
  "exp": 1743642000,
  "nonce": "abc123"
}
ConceptOAuth 2.0OIDC (adds to OAuth)
PurposeAuthorization (delegated access)Authentication (user identity)
TokenAccess token (opaque or JWT)ID token (always JWT)
ScopeCustom per APIopenid, email, profile
User infoNot standardized/userinfo endpoint

Access Tokens vs Refresh Tokens

TokenLifetimePurposeWhere It's Sent
Access TokenShort (5-60 min)Call APIs on behalf of userAuthorization: Bearer <token>
Refresh TokenLong (days-months)Get a new access token without re-loginPOST to token endpoint (server-side only)
ID TokenShort (matches access token)Prove user identity to the clientNever sent to APIs — only consumed by client
Warning: Never store refresh tokens in localStorage — they're long-lived and vulnerable to XSS. Use httpOnly cookies or server-side storage.

Test Yourself

What problem did OAuth 2.0 solve that didn't exist with simple password-based auth?

Before OAuth, third-party apps needed your actual password to access your data. OAuth lets you grant limited, revocable, scoped access via tokens — the app never sees your password.

What is PKCE and why is it needed?

PKCE (Proof Key for Code Exchange) prevents authorization code interception attacks. The client generates a random code_verifier, sends its hash (code_challenge) in the auth request, then proves it knows the original verifier when exchanging the code for tokens. An attacker who intercepts the code can't use it without the verifier.

What's the difference between an access token and an ID token?

Access token: sent to resource servers to access APIs — represents authorization. ID token: consumed only by the client app — represents the user's identity (contains claims like email, name). ID tokens should never be sent to APIs.

When would you use the Client Credentials grant instead of Authorization Code?

Client Credentials is for machine-to-machine communication where there's no human user — e.g., a backend service calling another API, a cron job, or a microservice. Authorization Code is for apps acting on behalf of a user.

Why is the Implicit flow deprecated?

The Implicit flow returns the access token directly in the URL fragment, making it vulnerable to token leakage (browser history, referer headers, shoulder surfing). Authorization Code + PKCE is strictly better — it keeps tokens out of URLs.

Interview Questions

A company's mobile app uses OAuth. A user revokes access via their Google account settings. The app's existing access token still works for 30 more minutes. Why? How would you fix this?

Access tokens are self-contained and validated locally (especially JWTs) — the resource server doesn't check with the auth server on every request. The token stays valid until it expires. Fix: use short-lived access tokens (5 min), implement token introspection for sensitive operations, or maintain a revocation list that resource servers check.

Explain the security difference between storing OAuth tokens in localStorage vs httpOnly cookies.

localStorage is accessible to any JavaScript on the page — an XSS attack can steal tokens. httpOnly cookies can't be read by JavaScript, so XSS can't exfiltrate them. However, cookies are vulnerable to CSRF — mitigate with SameSite=Strict/Lax and CSRF tokens. Best practice: httpOnly cookies for refresh tokens, short-lived access tokens in memory.

Your team is building a CLI tool that needs GitHub access. There's no browser available. Which OAuth flow would you use?

Use the Device Code flow. The CLI displays a URL and a code. The user opens the URL on any device with a browser, enters the code, and authorizes. The CLI polls the token endpoint until authorization completes. GitHub supports this flow for CLI tools like gh auth login.