đŸ›Ąïž Application Security CheatSheet

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

  1. Client (your app) sends the user to the Authorization Server (IdP).
  2. User logs in; IdP returns a code to your app’s callback URL.
  3. Your app exchanges the code for tokens: access token (API access) and optionally ID token (user identity).
  4. Your API (resource server) accepts access tokens only, and validates issuer/audience/expiry.
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)

B) Missing/weak state (login CSRF / session mix-up)

C) Missing/weak nonce (OIDC replay/substitution)

D) Token confusion (ID token vs access token)

E) Broken claim validation

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)

Don’t “test” OAuth by crafting payloads. Validate by reviewing configuration, callback binding, and claim checks.

Interview Questions & Answers (Easy → Hard)

Easy

  1. OAuth vs OIDC?
    A: Plain: OAuth grants access; OIDC logs users in. Deep: OIDC adds ID token + claims on top of OAuth.
  2. What is SSO?
    A: Plain: one login for many apps. Deep: centralized IdP issues tokens/assertions; apps validate and establish sessions.
  3. 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.
  4. What is state used for?
    A: Plain: prevents login-CSRF. Deep: binds request to response and prevents session mix-up.
  5. What is nonce used for?
    A: Plain: stops replay of identity tokens. Deep: binds ID token to the auth request in OIDC.
  6. 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.
  7. 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

  1. 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.
  2. Scenario: wildcard redirect URIs. Risk?
    A: Plain: code may land on attacker-controlled endpoint. Deep: leads to session takeover; enforce exact allowlist.
  3. Scenario: API accepts ID token. Why wrong?
    A: Plain: it’s not meant for APIs. Deep: audience/token type confusion; enforce access token audience.
  4. 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.
  5. Scenario: multi-tenant issuer. What checks matter?
    A: Plain: accept only expected tenant/issuer. Deep: strict iss/aud per tenant and safe account linking.
  6. Follow-up: do you rotate session at login?
    A: Plain: yes. Deep: prevents fixation and binds identity to fresh session.
  7. 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

  1. 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.
  2. How do you protect refresh tokens?
    A: Plain: store safely. Deep: rotate, revoke, bind to device/session, detect reuse.
  3. What telemetry do you add?
    A: Plain: watch failures. Deep: state/nonce mismatch spikes, redirect violations, token claim failures, unusual callback rates.
  4. 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.
  5. Scenario: IdP compromise. What’s your response?
    A: Plain: contain. Deep: revoke refresh/session state, shorten TTL, rotate keys, and audit suspicious logins.
  6. 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)

Defender win: strict redirects + state/nonce + token type separation kills most OAuth/OIDC attack chains.

Checklist

Remediation playbook

  1. Lock down redirect URIs to exact matches; remove wildcards/prefixes.
  2. Implement one-time state and OIDC nonce; store server-side and validate on callback.
  3. Rotate session on login and on account linking; bind identity to new session.
  4. Enforce token type separation (ID token vs access token) and strict claim validation.
  5. Add tests for state/nonce mismatch, wrong issuer/audience, expired tokens, and redirect enforcement.
  6. Monitor callback anomalies and repeated state failures.