OAuth 2.0 / OIDC & SSO
OAuth 2.0 lets an app get permission to call an API (âlet this app access my dataâ). OpenID Connect (OIDC) adds identity (âthis user is Surajâ). Together they power SSO.
Practical note: auth breaks most often at the seams â redirects, token storage, callback handling â not in the login form itself.
Beginner mental hook: OAuth is about access (scopes to APIs). OIDC is about login (ID token + session).
Common interview mistake: calling OAuth âauthentication.â Correct: OAuth=authorization; OIDC=authentication layer on OAuth.
Mental model
- Client (your app) sends the user to the Authorization Server (IdP).
- User logs in; IdP returns a code to your appâs callback URL.
- Your app exchanges the code for tokens: access token (API access) and optionally ID token (user identity).
- Your API (resource server) accepts access tokens only, and validates issuer/audience/expiry.
- state: binds the login request to the callback (prevents login-CSRF/session mix-up).
- nonce (OIDC): binds the ID token to the auth request (anti-replay/token substitution).
- redirect URI: must be strictly allowlisted; itâs where codes/tokens return.
experienced takeaway: Most âOAuth bugsâ are really validation and binding bugs: redirect URI, state, nonce, token claims, and session rotation.
Common failure modes (what goes wrong)
A) Redirect URI mistakes (account takeover chains)
- Root cause: overly broad redirect allowlists (wildcards, prefix matches, open redirects).
- How attackers think: âIf I can influence where the code/token lands, I can take over the session.â
- Defense: exact-match allowlist per client/environment; reject dynamic redirects.
B) Missing/weak state (login CSRF / session mix-up)
- Root cause: app doesnât validate state or uses predictable state.
- How attackers think: âCan I bind my login flow to the victimâs session?â
- Defense: cryptographically strong state, stored server-side, one-time use; rotate session on login.
C) Missing/weak nonce (OIDC replay/substitution)
- Root cause: ID tokens accepted without nonce binding.
- How attackers think: âCan I replay or swap an ID token into another session?â
- Defense: generate nonce per auth request; validate in ID token.
D) Token confusion (ID token vs access token)
- Root cause: API accepts ID token as if it were an access token, or doesnât enforce audience.
- Defense: resource servers accept access tokens with strict
aud; clients use ID tokens only for session establishment.
E) Broken claim validation
- Root cause: signature verified but
iss/aud/expnot strictly checked. - Defense: strict claim policy per issuer/client; pin algorithms and key sources.
Defensive patterns (Node.js concepts)
Vulnerable pattern (minimal)
// â Concept: accept callback without strict state/redirect checks
app.get("/oidc/callback", (req, res) => {
const code = req.query.code;
// exchanges code for tokens, but state not verified, session not rotated
res.redirect("/app");
}); Defensive pattern: strict binding + strict redirects
// â
Concept (library-specific details omitted):
// 1) Generate cryptographically strong state + nonce
// 2) Store them server-side (or signed+encrypted cookie), single-use
// 3) On callback: verify state matches, then exchange code
// 4) Validate ID token claims (iss/aud/exp) AND nonce
// 5) Rotate session at login and bind user identity to session
function startLogin(req, res) {
const state = crypto.randomUUID();
const nonce = crypto.randomUUID();
req.session.oidc = { state, nonce }; // single-use
res.redirect(buildAuthorizeUrl({ state, nonce, redirectUri: EXACT_ALLOWLISTED_URI }));
}
async function handleCallback(req, res) {
const { state, code } = req.query;
if (state !== req.session.oidc?.state) return res.status(400).send("invalid state");
const tokens = await exchangeCodeForTokens(code, EXACT_ALLOWLISTED_URI);
const idClaims = validateIdToken(tokens.id_token, { issuer, clientId, nonce: req.session.oidc.nonce });
req.session.regenerate(() => { // rotate session to prevent fixation
req.session.userId = idClaims.sub;
res.redirect("/app");
});
} Guardrails: exact redirect URI allowlist, one-time state/nonce, strict claim validation, and session rotation.
Safe validation (defensive verification)
- Confirm redirect URIs are exact-match allowlisted (no wildcards/prefix matching).
- Confirm
stateis per-request, unpredictable, stored server-side, and validated (single-use). - Confirm OIDC
nonceis generated and validated in the ID token. - Confirm ID token validation checks signature +
iss/aud/expand expected claims. - Confirm session rotation happens on login and privileged transitions.
- Confirm APIs accept access tokens only and enforce audience and scopes.
Donât âtestâ OAuth by crafting payloads. Validate by reviewing configuration, callback binding, and claim checks.
Interview Questions & Answers (Easy â Hard)
Easy
- OAuth vs OIDC?
A: Plain: OAuth grants access; OIDC logs users in. Deep: OIDC adds ID token + claims on top of OAuth. - What is SSO?
A: Plain: one login for many apps. Deep: centralized IdP issues tokens/assertions; apps validate and establish sessions. - Whatâs the authorization code flow?
A: Plain: get a code, exchange for tokens. Deep: reduces token exposure in the browser and supports stronger bindings. - What is
stateused for?
A: Plain: prevents login-CSRF. Deep: binds request to response and prevents session mix-up. - What is
nonceused for?
A: Plain: stops replay of identity tokens. Deep: binds ID token to the auth request in OIDC. - Redirect URI â why strict?
A: Plain: prevents sending codes/tokens to the wrong place. Deep: redirect mistakes are a common root cause of takeover chains. - ID token vs access token?
A: Plain: ID token = who; access token = what you can call. Deep: resource servers should accept access tokens only.
Medium
- Scenario: missing state validation. Risk?
A: Plain: login can be bound to victim session. Deep: session mix-up/login CSRF; fix with one-time server-stored state. - Scenario: wildcard redirect URIs. Risk?
A: Plain: code may land on attacker-controlled endpoint. Deep: leads to session takeover; enforce exact allowlist. - Scenario: API accepts ID token. Why wrong?
A: Plain: itâs not meant for APIs. Deep: audience/token type confusion; enforce access token audience. - Follow-up: how do you store state/nonce safely?
A: Plain: server session. Deep: server-side store or signed+encrypted cookie; single-use and short TTL. - Scenario: multi-tenant issuer. What checks matter?
A: Plain: accept only expected tenant/issuer. Deep: strict iss/aud per tenant and safe account linking. - Follow-up: do you rotate session at login?
A: Plain: yes. Deep: prevents fixation and binds identity to fresh session. - Scenario: public client (SPA). How do you harden?
A: Plain: avoid client secrets. Deep: use PKCE, strict redirects, short TTL, and prefer BFF for sensitive apps.
Hard
- How do you do secure account linking?
A: Plain: confirm user intent. Deep: require step-up, verify current session, validate provider identity, prevent linking takeover. - How do you protect refresh tokens?
A: Plain: store safely. Deep: rotate, revoke, bind to device/session, detect reuse. - What telemetry do you add?
A: Plain: watch failures. Deep: state/nonce mismatch spikes, redirect violations, token claim failures, unusual callback rates. - How do you handle key rotation/JWKS?
A: Plain: accept old+new briefly. Deep: cache JWKS, pin issuer mapping, monitor kid drift, and roll keys safely. - Scenario: IdP compromise. Whatâs your response?
A: Plain: contain. Deep: revoke refresh/session state, shorten TTL, rotate keys, and audit suspicious logins. - Follow-up: when choose sessions over pure tokens?
A: Plain: for web apps. Deep: sessions simplify revocation and CSRF controls; tokens need careful lifecycle design.
Exploitation progression (attacker mindset)
- Find the callback: which endpoint completes login?
- Look for binding gaps: missing state/nonce, session not rotated, weak redirect allowlist.
- Look for token confusion: can an ID token be used where access token is required?
- Look for claim mistakes: missing issuer/audience checks, overly permissive accepted issuers.
- Chain impact: session takeover, account linking abuse, privilege escalation via scopes/roles misuse.
Defender win: strict redirects + state/nonce + token type separation kills most OAuth/OIDC attack chains.
Checklist
- Exact redirect URI allowlist per environment/client.
- State: unpredictable, stored server-side, validated, single-use.
- Nonce: generated and validated for OIDC.
- ID token: signature + iss/aud/exp + nonce validated.
- Access token used for APIs; ID token not accepted by resource servers.
- Session rotation at login; safe account linking flows.
- Scopes minimized; step-up for sensitive actions.
Remediation playbook
- Lock down redirect URIs to exact matches; remove wildcards/prefixes.
- Implement one-time state and OIDC nonce; store server-side and validate on callback.
- Rotate session on login and on account linking; bind identity to new session.
- Enforce token type separation (ID token vs access token) and strict claim validation.
- Add tests for state/nonce mismatch, wrong issuer/audience, expired tokens, and redirect enforcement.
- Monitor callback anomalies and repeated state failures.