8 min read
Web Backend Fundamentals Index
Tier 1 -- Core Web & API Basics
Authentication & Authorization
Tier 2 -- Communication Models
Tier 3 -- Production & API Hardening
Web Backend Fundamentals Index
Tier 1 -- Core Web & API Basics
Authentication & Authorization
Tier 2 -- Communication Models
Tier 3 -- Production & API Hardening
OAuth and OIDC
1. What It Is
2.0 (RFC 6749) is an authorization framework. It lets a user grant a third-party application limited access to their resources on another service — without sharing their password. When you click "Sign in with Google" or authorize a GitHub app to read your repositories, that's .
OpenID Connect () is an authentication layer built on top of OAuth 2.0. OAuth answers "what can this app access?" answers "who is the user?" OIDC adds an ID token (a containing user identity information) to OAuth's access token.
In practice: OAuth 2.0 alone is for authorization (delegated access to APIs). OIDC is for authentication (verifying who the user is). Modern "login with Google/GitHub/Facebook" flows use both together.
Why it matters: most production apps use OAuth/OIDC for third-party login (social sign-in), or are themselves the resource server that accepts tokens from an identity provider (Auth0, Okta, AWS Cognito, Google). Understanding the flow lets you integrate securely and debug auth issues.
Your app needs to let users log in via Google and also retrieve their name and email to create a profile. Which protocol combination is most appropriate, and why?
2. How It Works in Practice
Key Roles
| Role | Who |
|---|---|
| Resource Owner | The user |
| Client | Your app (the one requesting access) |
| Authorization Server | The service that authenticates the user and issues tokens (Google, GitHub, Auth0) |
| Resource Server | The API that holds the user's protected resources |
Authorization Code Flow (the standard web app flow)
This is the most secure and most common flow for web applications.
Browser Your App (Client) Auth Server (Google)
| | |
|-- click "Login w/ Google" ->| |
| |-- redirect to Google ------>|
| | (with client_id, |
| | redirect_uri, scope, |
| | state, code_challenge) |
|<------- redirect to Google's login page ----------------|
|-- user logs in, grants permission -------------------->|
|<------- redirect back to your app --------------------|
| /callback?code=AUTH_CODE&state=... |
| | |
| |-- POST /token ------------->|
| | { code, client_secret, |
| | redirect_uri } |
| |<-- { access_token,---------|
| | id_token, |
| | refresh_token } |
| | |
| |-- GET /userinfo ----------->| (optional)
| |<-- { sub, email, name } ---|
Step by step:
- Your app redirects the user to the auth server's authorization endpoint with
response_type=code,client_id,redirect_uri, and requestedscope. - User logs in and grants permission on the auth server.
- Auth server redirects back to your
redirect_uriwith an authorization code (short-lived, single-use). - Your backend exchanges the code for tokens by calling the auth server's token endpoint — this call includes your
client_secretand happens server-side (the secret never reaches the browser). - Auth server returns
access_token,id_token(), and optionallyrefresh_token. - Your app uses the
access_tokento call protected APIs. Theid_tokenidentifies the user.
The state and PKCE parameters
state: a random value your app generates and sends with the auth request. The auth server echoes it back. You verify it matches — this prevents attacks on the callback.
PKCE (Proof Key for Code Exchange): for public clients (SPAs, mobile apps) that can't securely store a client_secret. The client generates a code_verifier (random string), hashes it to a code_challenge, sends the hash with the auth request, then sends the original verifier when exchanging the code. The auth server verifies they match — prevents authorization code interception attacks.
ID Token (OIDC)
The id_token is a containing claims about the authenticated user:
{ "iss": "https://accounts.google.com", "sub": "1234567890", ← unique user ID at this provider "email": "alice@gmail.com", "name": "Alice Smith", "iat": 1716000000, "exp": 1716003600, "aud": "your-client-id" ← must match your app's client_id }
Always verify: signature, iss (issuer), aud (audience), and exp. Use a library — don't parse manually.
In the OAuth 2.0 Authorization Code Flow, your backend receives an authorization code at the redirect URI after the user grants permission. Why must the subsequent token exchange request — where the backend trades the code for tokens — be made server-side rather than directly from the browser?
3. Common Real-World Usage
"Login with Google/GitHub": authorization code flow. Your app gets an ID token, extracts sub (stable user ID at the provider) and email, finds or creates a local user record, starts a .
API authorization: your API accepts Bearer tokens. A client app obtains tokens from an auth server (Auth0, Okta, Cognito) and sends them to your API. Your API verifies the signature using the provider's public keys (fetched from the JWKS endpoint).
Machine-to-machine (M2M): backend services calling other services use the Client Credentials flow — no user involved. The service authenticates with its own client_id and client_secret, gets an access token for the downstream API.
Managed identity providers: Auth0, Okta, AWS Cognito, Clerk, Firebase Auth — you configure them with your app credentials, and they handle the auth server role. You focus on verifying their tokens in your API.
A backend service needs to call a downstream internal API to process data — there is no user logged in and no browser involved. Which OAuth flow is most appropriate for this scenario?
4. Trade-offs and Common Mistakes
Implicit Flow is deprecated: the implicit flow (response_type=token) sends tokens directly in the URL fragment. PKCE + authorization code flow is the modern replacement for SPAs and mobile apps. Never use implicit flow for new apps.
Not validating the state parameter: skipping state validation makes your callback vulnerable to — an attacker can trick a user into linking an attacker-controlled account.
Trusting the sub alone without checking iss: the sub claim is unique per provider but not globally unique. A sub from Google and a sub from GitHub may clash. Always store (iss, sub) as the user identity key, not just sub.
Storing client_secret in browser code: the secret must stay on the server. For public clients (SPAs, mobile apps), use PKCE — there is no secret.
Not rotating refresh tokens: if you issue long-lived refresh tokens, implement rotation (invalidate the old token when a new one is issued). Some providers support refresh token families that detect reuse.
Using access tokens as tokens in cookies: access tokens are for calling APIs, not for browser management. After login, start a server-side session (with your own session ) tied to the user identity from the ID token.
Your app authenticates users via two different OIDC providers (Google and GitHub). You store each user in a database keyed only by their sub claim. A user who logs in with Google has sub = '12345'. Later, a different user logs in with GitHub, and their sub is also '12345'. What is the consequence of this design?
5. Interview Angle
How to explain it
" 2.0 is an authorization delegation framework — it lets a user grant your app access to their resources on another service without sharing their password. layers identity on top: it adds an ID token (a ) that tells your app who the user is. The authorization code flow is the standard: your app redirects the user to the provider, they log in, the provider redirects back with a short-lived code, and your backend exchanges the code (plus your client_secret) for tokens."
Common interview questions
Q: What is the difference between 2.0 and ?
A: OAuth 2.0 is about authorization — delegating access to resources. It doesn't tell you who the user is. OIDC extends OAuth 2.0 to add authentication — it introduces the ID token, a containing the user's identity claims (sub, email, etc.).
Q: Why is the authorization code flow more secure than the implicit flow? A: In the authorization code flow, tokens are never exposed in the browser URL — the browser only receives a short-lived code, and the actual token exchange happens server-side with the client secret. In the implicit flow, the access token appeared in the URL fragment (visible in browser history, referrer headers, logs). That's why implicit flow is deprecated.
Q: What is PKCE and why is it needed?
A: Proof Key for Code Exchange — a mechanism for public clients (SPAs, mobile apps) that can't securely store a client_secret. It uses a cryptographic challenge/verifier pair to prove the token request comes from the same client that initiated the auth flow, preventing authorization code interception.
Q: How does your API verify a third-party access token (e.g., from Auth0)?
A: The identity provider publishes its public keys at a JWKS (JSON Web Key Set) endpoint (e.g., https://your-tenant.auth0.com/.well-known/jwks.json). Your API fetches these keys, verifies the token signature, checks iss, aud, and exp. Libraries (e.g., jsonwebtoken, python-jose) do this with the JWKS URL.
Q: What is the Client Credentials flow?
A: Used for machine-to-machine (M2M) auth where there's no user involved. Service A sends its client_id and client_secret directly to the auth server's token endpoint and gets an access token to call Service B. No authorization redirect needed.
A mobile app needs to implement OAuth 2.0 login but cannot securely store a client_secret (since the app's binary can be reverse-engineered). Which mechanism allows it to safely use the authorization code flow?
6. Practical Examples
OIDC login with Google (Node.js / Express using openid-client)
const { Issuer } = require('openid-client'); // One-time setup: discover provider metadata const googleIssuer = await Issuer.discover('https://accounts.google.com'); const client = new googleIssuer.Client({ client_id: process.env.GOOGLE_CLIENT_ID, client_secret: process.env.GOOGLE_CLIENT_SECRET, redirect_uris: ['https://myapp.com/auth/callback'], response_types: ['code'], }); // Step 1: redirect to Google app.get('/auth/login', (req, res) => { const state = crypto.randomUUID(); req.session.oauthState = state; const url = client.authorizationUrl({ scope: 'openid email profile', state }); res.redirect(url); }); // Step 2: handle callback app.get('/auth/callback', async (req, res) => { const params = client.callbackParams(req); if (params.state !== req.session.oauthState) return res.status(400).send('State mismatch'); const tokenSet = await client.callback('https://myapp.com/auth/callback', params, { state: req.session.oauthState, }); const claims = tokenSet.claims(); // { sub, email, name, ... } const user = await findOrCreateUser({ sub: claims.sub, iss: claims.iss, email: claims.email }); req.session.userId = user.id; res.redirect('/dashboard'); });
Verifying a JWT from Auth0 (Python)
from jose import jwt from jose.exceptions import JWTError import httpx JWKS_URL = 'https://your-tenant.auth0.com/.well-known/jwks.json' AUDIENCE = 'https://api.myapp.com' ISSUER = 'https://your-tenant.auth0.com/' def verify_token(token: str) -> dict: jwks = httpx.get(JWKS_URL).json() try: payload = jwt.decode( token, jwks, algorithms=['RS256'], audience=AUDIENCE, issuer=ISSUER, ) return payload except JWTError as e: raise PermissionDenied(f'Invalid token: {e}')
Client Credentials flow (M2M, Python)
import httpx def get_service_token() -> str: resp = httpx.post( 'https://your-tenant.auth0.com/oauth/token', json={ 'grant_type': 'client_credentials', 'client_id': CLIENT_ID, 'client_secret': CLIENT_SECRET, 'audience': 'https://order-service.internal/', }, ) return resp.json()['access_token'] # Use in service-to-service calls token = get_service_token() httpx.get('https://order-service.internal/orders', headers={'Authorization': f'Bearer {token}'})
A backend service needs to call another internal service (order-service) without any user being involved in the request. Which OAuth 2.0 grant type is appropriate for this machine-to-machine scenario, and what credential does the calling service present to obtain an access token?
Glossary History
Click dotted jargon to save explanations here.
Glossary History
Click dotted jargon to save explanations here.