SwadhyAI Notes

All System Design notes

System Design · Security

OAuth & JWT

A SPA shows "Continue with Google"; a mobile app opens ChatGPT-style login. Users should never type their identity-provider password into your form, and a stolen redirect must not hand attackers a lasting session.

delegates access with limited tokens. is a common token format. Production systems pair + for users and short-lived access tokens with rotating refresh families.

Sign-in journey

1

Create verifier + challenge before redirect.

2

IdP returns a short-lived code.

3

Exchange code + verifier; call APIs.

4

Detect reuse; revoke on theft.

What actually happens

The app redirects the user to the IdP with a challenge. After login and consent, the browser returns an . Only the party holding the original verifier can exchange it for an and .

APIs verify JWT signatures via , then check issuer, audience, expiry, and scopes. adds an ID token for “who logged in.” Browsers often store sessions in an ; putting bearer tokens in localStorage makes XSS catastrophic.

Machine callers use . Logout and theft need a plan: short TTL, refresh , denylists, or session versions, not “JWT means forever valid.” Higher-risk APIs may add so a stolen bearer alone is not enough.

Try it: OAuth authorization code flow with JWT checks

Step through PKCE, the authorization code, token exchange, and JWT validation on the resource server. This models defensive token handling, not token theft.

Flow step
0 / 6
PKCE verifier
Not created
Access token TTL
—
JWT checks
—
  1. 1Create PKCE verifier
  2. 2Redirect to IdP
  3. 3Authorization code returned
  4. 4Code + verifier exchange
  5. 5Validate JWT claims
  6. 6Call resource API

User agent

Browser
  • Verifier stored in app memory
  • IdP login + consent
  • One-time code in redirect

Token endpoint

Back channel

Validates code, verifier, client, and redirect URI match.

Waiting for exchange

Resource server

JWKS

Pending: Signature via JWKS

Pending: iss / aud match

Pending: exp not passed

Pending: scope includes read:profile

This is the authorization code flow with PKCE on the happy path. Advance each step; only the holder of the original verifier can finish the exchange.

PKCE. Public clients keep the verifier secret so an intercepted code cannot be exchanged by a stranger.

JWT validation. Signature plus issuer, audience, expiry, and scope checks are mandatory on every request, not optional debugging.

Correctness invariant

What must never become false

A resource server accepts an access token only if signature, issuer, audience, expiry, and required scopes all validate, and algorithm is on an allow-list (no "none" / unexpected alg). Public clients use authorization code + (RFC 9700); refresh-token reuse in a revokes the family. Stolen bearer alone must not be enough for higher-risk APIs when sender-constrained tokens (DPoP/mTLS) are required.

Quantitative worked example

  • Access token TTL 5 min vs 24h: stolen JWT window shrinks ~288×; refresh traffic rises (one refresh per ~5 min of active use).
  • JWKS fetch cached 10 min: 10k RPS verifying locally ≈ 0 remote JWKS QPS steady; rotate keys with overlap so verification does not fail mid-roll.
  • Refresh reuse detection: if attacker and victim both use the same refresh once, revoke the whole family: blast radius is the session, not one access token TTL.

Failure timeline: refresh theft and family revoke

T0  User completes auth code + PKCE; gets access + refresh (family F)
T1  XSS / malware steals refresh from insecure storage (localStorage anti-pattern)
T2  Attacker redeems refresh → new access; rotation issues refresh′
T3  Victim app still holds old refresh; redeems it → reuse detected
T4  RFC 9700 path: revoke entire family F; both sides lose session
T5  Without rotation/reuse detection: attacker keeps minting access until manual revoke

Counterexample to "JWT means we cannot revoke": short access TTL plus rotating refresh families (and optional denylist) is the production revoke story: aligned with RFC 9700.

How to choose OAuth pieces

1. Public or confidential client? SPAs and mobile use auth code + PKCE per RFC 9700. Never resurrect implicit or password grants.

2. Instant logout or local verify? Opaque sessions revoke centrally; JWTs scale reads but need short TTL or extra revocation machinery.

3. Where do tokens live? Prefer httpOnly cookies for classic web apps; OS secure storage on mobile; never paste secrets into JS-readable storage by default.

Simple rule of thumb

Ship PKCE for user clients, asymmetric JWT verification with algorithm allow-lists, rotating refresh families, and an explicit answer for “how do you kill access before expiry?” Follow RFC 9700 defaults: no implicit/password grants, validate issuer and audience, detect refresh-token reuse.

In simple terms: borrow identity carefully, keep tokens short-lived, and treat reuse as theft.

OAuth 2.0 flows

Match the flow to the client trust boundary

FlowClientSecurityTypical use
Auth code + PKCESPA, mobile, server-side appsHighest for public clientsLogin UX for end users
Client credentialsConfidential backendHigh (secret rotation)Microservice → API
Device codeTV, CLI, printersMedium (out-of-band consent)Limited input UX
ImplicitLegacy SPADeprecatedDo not build new

Authorization Code + PKCE (recommended)

  1. Client generates high-entropy code_verifier.
  2. Sends code_challenge = BASE64URL(SHA256(verifier)) with the authorize redirect.
  3. User authenticates at IdP; browser returns authorization code to registered redirect.
  4. Token endpoint receives code + verifier; IdP hashes verifier and compares: blocks intercepted-code replay without static client secrets on devices.

Device authorization grant

  • Device requests device_code + user-readable code.
  • User completes login on a secondary device; constrained gadget polls token endpoint until approval or timeout.
  • Powers streaming-on-TV, developer CLI, speaker onboarding flows.

PKCE request sequence

Protect the authorization code, then constrain token blast radius

Diagram text: the exact Mermaid definition below preserves the nodes, labels, and connections. Enable JavaScript to view the rendered diagram.
Read the diagram definition
sequenceDiagram
    autonumber
    participant Browser
    participant App
    participant IdP as Identity provider
    participant API

    App->>Browser: Generate code_verifier and redirect
    Browser->>IdP: Authorize with code_challenge
    IdP->>Browser: Login, consent, MFA
    IdP-->>App: Redirect with authorization code
    App->>IdP: Exchange code plus verifier
    IdP-->>App: Access token and refresh token
    App->>API: Call API with access token
    API-->>App: Verify signature, audience, scope, expiry

Why PKCE exists

PKCE binds an authorization-code exchange to the client instance that began the flow. The client creates a random verifier, sends its derived challenge with the authorization request, and later proves possession of the verifier when redeeming the returned code. An intercepted code alone is insufficient. Continue validating the redirect URI and transaction binding: PKCE does not make arbitrary redirect destinations or untrusted identity providers safe.

Token tradeoff

A JWT lets a resource server verify signed claims locally, reducing dependence on a session lookup for every request. That convenience creates a freshness boundary: a token can remain valid until expiry even after the account’s permissions change. Choose a short useful lifetime and an explicit revocation mechanism when the product requires faster removal. Refresh-token rotation prevents continued renewal; it does not automatically invalidate existing access tokens.

OAuth and JWT threat model

Assets
Authorization codes, access tokens, refresh tokens, signing keys.
Actors
End users, public clients, confidential clients, token thieves.
Trust boundaries
Browser redirect, token endpoint, API gateway, signing-key cache.
Attack paths
Redirect capture, token replay, confused audience, stale keys.
Controls
PKCE, exact redirect URI, short TTL, refresh rotation, algorithm allow-list.
Detection
Refresh reuse, impossible travel, unusual scopes, key-fetch failures.
Residual risk
Stolen access tokens remain usable until expiry if revocation lookup is unavailable.

JWT structure & hardening

Signed, not encrypted: anyone can Base64-decode the payload

Three-part token

header.payload.signature: verify signature with JWKS, confirm algorithm matches allow-list (reject alg:none). The configured issuer determines which trusted keys and algorithms are allowed; the token must not choose an arbitrary key source. After verifying the signature, check audience, expiry, and other required claims for this endpoint and token type. A valid signature means an issuer signed these claims, not that this token was intended for your API or grants access to every resource.

Header
alg, typ, kid
Claims
sub, iss, aud, exp, optional scope/roles
Signature
asymmetric algorithms asymmetric preferred

Do

  • Keep access tokens short-lived (minutes).
  • Rotate signing keys with kid + JWKS publishing.
  • Validate issuer, audience, not-before, expiry on every request path.

Don't

  • Drop secrets or PII into JWT bodies: they're readable.
  • Trust client-selected algorithms.
  • Assume JWT revocation works without extra machinery.

Token storage: browsers

httpOnly cookies vs bearer in JS-accessible storage

httpOnly + Secure cookies

  • Pros: JavaScript cannot read the token: major XSS mitigation; cookies ride automatically to your API origin when scoped.
  • Pair with: SameSite (Lax/Strict) + CSRF tokens for mutating verbs.
  • Watch: ~4 KB cookie budgets; cross-domain setups need careful BFF or shared-site patterns.

localStorage / sessionStorage

  • Risk: any XSS exfiltrates bearer tokens: game over until rotation.
  • Mitigation: tight CSP, nonce scripts, strip dangerous sinks, still weaker than httpOnly for classic web apps.
  • Mobile: use OS secure enclave / keystore equivalents, not plaintext SharedPreferences.

Token storage decision matrix

ClientStorageMain riskRefresh strategyLogout behavior
Browser with backendhttpOnly, Secure, SameSite cookieCross-Site Request Forgery (CSRF)Server-side session or refresh familyCentral session kill is fast
Pure browser appMemory plus short access tokenCross-Site Scripting (XSS)Refresh through guarded endpointLimited by token expiry
Mobile appOS secure storageDevice compromise or backup leakRotating refresh token bound to deviceRevoke refresh family and device binding
Command-line clientEncrypted local credential storeShared workstation or copied fileDevice-code refresh with short scopesRevoke client grant
Server-to-serverSecret manager or workload identityLong-lived secret leakShort-lived client credential tokenDisable key or workload identity

JWT vs session trade-offs at scale

Token size, revocation, cache pressure, and where state lives

Proof type

Opaque session cookie

Signed JWT access token

ClientProof choiceSession pathJWT pathAPI / audit

Token size budget

A compact access token with common claims often lands around 0.8-2 KB. At 50k requests/sec, adding 1 KB to every request is ~50 MB/sec of extra ingress before retries.

Session lookup cost

Opaque cookies keep the wire small but add a cache lookup. Replicate hot session shards by region, keep TTLs tight, and budget p99 latency for cache misses.

Revocation story

An opaque session can be centrally revoked by changing its server-side record, although regional caches can delay enforcement. A locally verified JWT avoids that lookup but usually remains usable until expiry unless the API also checks a denylist or session version. Decide the acceptable revocation delay first, then choose where the extra lookup belongs. Neither format removes the need to authorize the requested object.

Choose deliberately

For browser apps, prefer an httpOnly same-site cookie with server-side session state when instant logout and small requests matter. For high-volume service APIs, short-lived signed tokens reduce central lookups, but only if every service validates issuer, audience, expiry, and algorithm consistently.

JWT validation checklist

Validate issuer, audience, expiry, not-before, algorithm allow-list, key id, signature, token use, scope, tenant, clock skew, and replay identifier where needed. Cache signing keys, but keep overlap windows during rotation so old tokens do not fail suddenly.

Revocation memory math

10M active sessions with 15-minute access tokens and 0.01% daily suspicious sessions means ~1,000 deny entries/day. At 160 bytes/entry × 2 replicas, memory is small, but write amplification and lookup latency matter. If compromise spikes to 100k entries/hour, keep TTL equal to remaining token lifetime and shed low-value dimensions first.

Runbook: signing-key discovery outage

  • Continue validating cached keys only until their bounded stale window expires; do not fetch keys synchronously on every request.
  • Fail closed for admin and money-moving scopes, while allowing low-risk cached sessions only if policy explicitly permits it.
  • Page identity owners on key-fetch error rate, unknown kid spikes, and issuer metadata drift.
  • Replay a token-validation test suite after key rotation: old valid token, new valid token, expired token, wrong audience, wrong algorithm.

Refresh token rotation

Detect reuse → revoke token families

  1. Issue access token (~15 min) + refresh token bound to a server-side family identifier.
  2. On refresh, mint new refresh token and invalidate the previous hash atomically.
  3. If a revoked refresh token appears again, assume theft: revoke entire family and force step-up login.

Storage tip

Store a cryptographic hash of each high-entropy refresh token rather than the reusable raw secret, together with its owner, expiry, family, and replacement state. Rotate the token atomically when used so concurrent requests cannot both create independent successors. If an already-used token reappears, the server can revoke the affected family and require fresh authentication. Carefully define client retry handling so a lost response does not silently create an uncontrolled branch.

Refresh token family state machine

Active token rotates to a new active token; the previous hash becomes rotated. If a rotated token is reused, mark the family compromised, revoke every descendant, and require step-up login. Expired families are deleted after retention and audit windows.

Stateless validation vs revocation

Pick explicit trade-offs per product

Short TTL + refresh

If access tokens are verified locally for fifteen minutes, revoking the refresh family prevents new access tokens but can leave an already-issued token usable for its remaining lifetime. For a product that requires immediate account lock, add a sufficiently fresh session-version or revocation check at enforcement points. Make that residual window visible in the design rather than describing refresh revocation as instant logout everywhere.

Denylist / bloom

Push revoked jti into in-memory datastore with TTL = remaining JWT lifetime: hybrid stateless + selective recall.

Session version

Embed monotonic version claim; bump on logout: requires cached lookup but bounded cost vs full session DB round-trip.

OpenID Connect: identity on OAuth

ID tokens vs access tokens

What OIDC adds

  • Standard ID Token (JWT) naming the authenticated subject.
  • Discovery doc (/.well-known/openid-configuration) + JWKS URI for integrators.
  • Optional UserInfo endpoint for supplemental claims.

Validate ID tokens

  • Check nonce ties to original authorize request (replay resistance).
  • Confirm aud matches your client id.
  • Prefer fetching signing keys via JWKS with caching + backoff, not hard-coded PEM files.

Reference architectures

How platforms compose OAuth surfaces

Payment-style APIs

Secret vs publishable keys, OAuth for Connect marketplaces, webhook signatures (HMAC) for integrity: layer alongside scoped RBAC inside dashboards.

Developer platform ecosystem

OAuth apps vs installation-based installation tokens, device flow for CLI, fine-grained PATs replacing overly-wide legacy tokens: study scopes before interviews.

Case studies

Redirects, token confusion, and why PKCE + JWKS hygiene moved from optional to mandatory

OAuth 2.0 · redirect attacks

Stealing authorization codes via redirect_uri confusion

OAuth sends users through an identity provider with a redirect_uri describing where to deliver the short-lived authorization code. Misconfigured registrations: wildcard hosts, http localhost leftovers, or subdomain takeover: let attackers harvest codes before your backend exchanges them for tokens.

Beginners: imagine the auth code as cash handed through a drive-through window, if the cashier shouts “whoever grabs it first gets soda,” PKCE and strict redirect URIs are the armored tube preventing snatch-and-run.

Simplified attack / failure chain

  1. Attacker discovers permissive redirect pattern or stale subdomain pointing to their infra.
  2. Victim completes login at IdP → code issued to attacker-controlled redirect.
  3. Attacker exchanges code at token endpoint unless PKCE/code_verifier mismatches block them.
  4. Ops discovers suspicious OAuth client telemetry days later via audit lag.

Advanced controls & design pressure

  • Exact-match redirect URIs; CI lint preventing wildcard merges.
  • Mandatory PKCE for native + SPA, even “confidential-ish” hybrids mishandle secret storage.
  • Rotate clients per environment; denylisted hosts after acquisitions spin-down.
  • Monitor authorize latency spikes per client: bots brute-force consent URLs.
  • Use PAR/JAR patterns where phishing resistance budgets allow.

Aftermath, signals & interview questions

security guidance hardened defaults: interviews expect PKCE fluency for mobile in 2026.

JWT · verification bugs

Algorithm confusion & weak asymmetric validation paths

Libraries historically accepted multiple algorithms per token header: attackers forged HS256 tokens using the server’s own RSA public key material as HMAC secret, or slipped alg=none past lax validators.

Beginner mantra: Base64 JSON printing is not verification: cryptographic checks plus audience binding separate toy demos from production gates.

Simplified attack / failure chain

  1. Attacker obtains public key advertised via JWKS.
  2. Mints HS256 token using pubkey bytes as symmetric secret: servers trusting header alg blindly accept.
  3. Elevated claims slip past RBAC expecting integrity guarantees.
  4. Discovery occurs via anomaly detection or external researcher.

Advanced controls & design pressure

  • Hard allow-list algorithms per issuer + reject unknown kid fast.
  • Separate symmetric vs asymmetric verification codepaths, never reuse structs.
  • Strict iss/aud/azp/typ enforcement.
  • Central JWT verification SDK pinned semver-wide: avoid seventeen forks.
  • Nonce-bound exchanges where replay matters (OIDC hybrid confusion defenses).

Aftermath, signals & interview questions

  • identity provider/identity provider advisories repeatedly stress alg pinning: cite them in interviews.
  • Add observability: histogram JWT verification failures by reason code.

Session fixation · cookies

Browser cookie stacks: Secure, HttpOnly, SameSite hardening

XSS attackers read document.cookie unless cookies are HttpOnly; CSRF attackers forge posts unless SameSite + CSRF synchronizer tokens block ambient authority. Meanwhile enterprise SSO stacks often juggle dozens of cookies across parent domains: easy to mis-scope.

Beginners map threats: stolen JS execution ⇒ confidentiality loss; cross-site form posts ⇒ unwanted mutations: cookies participate in both sagas. HttpOnly prevents JavaScript from reading a cookie directly, but injected same-origin JavaScript can still make authenticated requests through the browser. CSRF protection addresses a different path: another site causes the browser to send credentials with a request. Use appropriate cookie settings, CSRF defenses, and origin checks together with XSS prevention; selecting a token-storage location does not eliminate all browser threats.

Simplified attack / failure chain

  1. Attacker plants XSS on subdomain sharing lax cookie scope.
  2. Session theft enables API replay from attacker-controlled origins.
  3. Alternatively CSRF rides silent cookie inclusion on cross-site POST.
  4. Blue teams patch XSS while tightening cookie prefixes + SameSite=Lax defaults.

Advanced controls & design pressure

  • __Host-/__Secure- prefixes where supported.
  • CHIPS partitioning + dedicated registrable domains for admin consoles.
  • BFF pattern hiding tokens from browsers entirely: SPA talks same-site only.
  • Refresh-token rotation families detecting reuse indicative of theft.
  • CSP + Trusted Types reduce XSS probability: the cookie flags are backup singers.

Aftermath, signals & interview questions

Browser defaults steadily tightened third-party cookie behavior: frontend architects must regression-test login quarterly.

Supply chain · package ecosystem

Malicious OAuth helper packages: convenience package installs with exfil hooks

Typosquatted libraries mimic popular OAuth helpers, silently wrapping fetch/token exchanges to exfiltrate credentials to attacker domains: often buried postinstall scripts evading casual diff review.

Beginners recognize OAuth complexity drives copy-paste engineering, without internal SDKs, every junior repo reinvents footguns.

Simplified attack / failure chain

  1. Developer package installs slightly wrong package name.
  2. Wrapper proxies authorize/token endpoints copying secrets outbound.
  3. Telemetry-blind until billing anomalies or bug bounty reports surface.
  4. Remediation requires rotating every embedded client secret org-wide.

Advanced controls & design pressure

  • Scoped internal package namespace + enforced dependency review bots.
  • Central auth software development kit with versioning and security champions reviewing releases.
  • OIDC metadata pinning inside CI tests: detect swapped issuer URLs.
  • Treat OAuth clients like workloads: workload identity IDs fetching secrets from vault.
  • Runtime egress deny lists blocking unexpected OAuth token endpoints.

Aftermath, signals & interview questions

Supply-chain compromises pushed finance and legal teams to audit dependency inventory gates blocking merges.

OAuth 2.0 · device flow · phishing evolution

Device authorization phishing: “enter this code” trains users into MFA-fatigue patterns

Device flows let TVs or CLI tools authenticate without browsers: users visit a URL and paste short codes. Attackers replicate UX on fake sites capturing codes mid-session, akin to older OTP phishing but tightly coupled to OAuth ergonomics.

Beginners pair this with user education limits: assume users will paste codes into the wrong tab unless bindings tie displays cryptographically.

Simplified attack / failure chain

  1. Attacker triggers legitimate device flow on victim tenant branding clone.
  2. Victim copies “legitimate-looking” approval portal URL via spear-phish.
  3. Malicious portal relays codes upstream faster than human scrutiny.
  4. Tokens minted for attacker-controlled device_id equivalents.

Advanced controls & design pressure

  • Show binding confirmation on primary enrolled device (“did you start CLI login?”).
  • Rate-limit device-code issuance per IP + impossible-travel heuristics.
  • Org-wide login-page branding: distinct colors/logos IdPs inject on verifying portals.
  • Prefer CAEP/RISC-style shared signals across tenants where available.
  • Educate developers never embedding device flows without phishing-focused UX reviews.

Aftermath, signals & interview questions

repeated abuse variants make good fodder for “human-in-loop MFA vs machine flows.”

Audit deepening: RFC 9700 and sender-constrained tokens

Align with OAuth 2.0 Security Best Current Practice (RFC 9700): avoid deprecated implicit and resource-owner password grants; require authorization code + PKCE for public clients; validate iss and aud; rotate refresh tokens and revoke the family on reuse detection.

For higher-risk APIs, prefer sender-constrained access tokens via DPoP or mTLS token binding so a stolen bearer alone is not enough, also recommended by RFC 9700 for sensitive deployments. Sender constraints bind token use to proof of possession of a key, rather than allowing any holder of the token string to use it. They reduce the value of a token stolen without its associated key. The resource server must validate the proof and relevant request binding, and key theft or malicious code running in the legitimate client remains a different threat. Adoption therefore needs key lifecycle and replay-handling design, not only another token claim.

JWT is a format (RFC 7519), not a security model: still enforce algorithm allow-lists, JWKS rotation, short access-token TTL, and an explicit logout/revocation story.

Exercises

1. RFC 9700 review

A SPA PR uses the implicit grant "because it is simpler." Cite RFC 9700 and rewrite the flow to auth code + PKCE, naming where the code verifier lives and what is stored in cookies vs memory.

2. Logout before expiry

Access tokens are 15 minutes, no denylist. User clicks logout after account takeover fear. Design the revoke path (refresh family, session version, or sender-constrained tokens) and the residual risk window.

3. Audience confusion

Service A accepts tokens whose aud is Service B. Explain the attack and the exact validation rule you enforce at every resource server.

Topic-specific references

Short takeaway

Ship PKCE for user clients, asymmetric JWT verification, httpOnly cookies on web, rotating refresh families, and an explicit plan to revoke access before expiry: tied to product risk and RFC 9700, not buzzwords.

Practice and further reading

Use these exercises to connect the lesson to a design decision. State your assumptions, trace what happens to one request, and explain the rule that must remain true even during a failure. That rule is the design's correctness invariant.

Focus for this lesson: Use authorization code with PKCE, strict issuer/audience validation, short-lived access tokens, rotation, revocation, and sender constraints.

Explain the decision

Why should an API validate JWT audience as well as signature?

Read the reasoning

A valid token minted for another service is not authorization for this API; issuer, audience, time, algorithm, and required claims all matter.

Apply it to a production scenario

A stolen refresh token is reused after rotation. What should happen?

Read one approach

Detect reuse of the invalidated family member, revoke the token family/session, notify and reauthenticate the user, and retain audit evidence.

Work through the numbers

At 50,000 token validations/s and 99.9% local key-cache hit rate, how many key fetches/s remain?

Read the worked calculation

About 50 fetches/s; also coalesce refreshes and serve last-known-good keys through brief identity-provider outages.

Reason through a failure

What must PKCE preserve?

Read the recovery reasoning

The authorization response code is redeemable only by the client instance holding the original verifier, reducing intercepted-code abuse.