OAuth 2.0 for systems analyst interviews

Train for your next tech interview
1,500+ real interview questions across engineering, product, design, and data — with worked solutions.
Join the waitlist

Why systems analysts get grilled on OAuth

Almost every non-trivial integration a systems analyst writes up touches OAuth 2.0. Payment webhooks, a microservice calling a partner API, a mobile app logging users in through Google — they all sit on top of the same handful of grant types, the same token lifecycles, and the same mistakes that interviewers at Stripe, Uber, Notion, and Airbnb hear three times a week. A spec that says "the client authenticates via OAuth" without naming a flow is not a spec — it is a wish.

The interview itself starts soft: "draw me Authorization Code". Then it gets specific fast — PKCE, refresh rotation, JWT validation, token lifetimes. The candidates who fail are not the ones who confuse OAuth with OIDC. They are the ones who say "the mobile app stores the client_secret" or "we put the access token in the URL because the backend logs it for us". One sentence, and the interviewer has their answer.

Load-bearing rule: OAuth is about what is allowed. OIDC is about who the user is. Mix these on the whiteboard and every follow-up gets harder.

The four roles you must name in 10 seconds

Before any flow diagram, name the four roles. Interviewers use this as a smoke test — if you stumble here, they cut the OAuth section short and you lose points before the real questions begin.

Role What it is Concrete example
Resource Owner The human who owns the data A Spotify user
Client The app requesting access A third-party playlist analyzer
Resource Server The API holding the data Spotify's /v1/me/playlists API
Authorization Server The service issuing tokens Spotify's accounts.spotify.com

In small systems the Resource Server and Authorization Server collapse into one deployment. On a serious platform — Google, Auth0, Okta, AWS Cognito — they are separate services with separate scaling profiles.

The trap is calling the Client the "user" because a human sits in front of it. The Client is the application; the human is the Resource Owner.

Authorization Code Flow

This is the workhorse for web apps and any mobile app that has a backend. Expect to draw it on a whiteboard within the first five minutes of the OAuth round.

1. Client redirects User to Authorization Server:
   GET /authorize?response_type=code
     &client_id=abc
     &redirect_uri=https://app.example.com/callback
     &scope=read:photos
     &state=xyz

2. User logs in to the Authorization Server and consents.

3. Authorization Server redirects back:
   https://app.example.com/callback?code=AUTH_CODE&state=xyz

4. Client (server-side!) exchanges code for tokens:
   POST /token
   grant_type=authorization_code
   &code=AUTH_CODE
   &redirect_uri=https://app.example.com/callback
   &client_id=abc
   &client_secret=SECRET

5. Authorization Server returns:
   { "access_token": "...", "refresh_token": "...", "expires_in": 900 }

6. Client calls Resource Server:
   Authorization: Bearer <access_token>

Why two hops — code, then token? The code travels through the user's browser, which is observable: extensions, history, referer headers, screen recordings. The token exchange happens server-to-server with the client_secret. The browser never sees the long-lived credential.

Sanity check: Always pass and verify the state parameter. It binds the redirect to the original session and blocks CSRF on the callback. A spec that omits state will get red-pen treatment.

Authorization Code with PKCE

PKCE (Proof Key for Code Exchange, RFC 7636) is the extension for public clients — SPAs, native mobile apps, desktop apps — anything that cannot safely store a client_secret. As of 2026, the OAuth 2.1 draft makes PKCE mandatory for all Authorization Code flows, even confidential clients. Treat it as the default and you will never be wrong.

The shape changes in two places:

Before /authorize:
  code_verifier  = random 43-128 char string
  code_challenge = BASE64URL(SHA256(code_verifier))

In /authorize:
  ...&code_challenge=<hash>&code_challenge_method=S256

In /token:
  ...&code_verifier=<original random string>

The Authorization Server stores the code_challenge when it issues the code, and on the token exchange it recomputes SHA256(code_verifier) and compares. An attacker who intercepts the redirect on a mobile device — via a malicious app registered for the same custom scheme — gets the code but not the verifier, and the exchange fails. Always use S256, never plain.

Client Credentials Flow

This is the service-to-service flow. No human, no browser, no consent screen — just one backend calling another with a shared secret.

POST /token
grant_type=client_credentials
&client_id=svc-billing
&client_secret=SECRET
&scope=invoices:read invoices:write

Returns an access_token, usually no refresh token. When it expires, you call /token again. Typical use cases:

  • An internal microservice at Notion calling the search service
  • A nightly Snowflake job pulling from a partner's reporting API
  • A Vercel serverless function fanning out to a payment provider

The big rule: never use Client Credentials when there is a real human user behind the request. The token has no sub for an end user, so every downstream audit log will read "service made this change" instead of "Alice made this change". For real users you want Authorization Code; for genuinely user-less jobs you want Client Credentials. Mixing them is a common spec smell.

Train for your next tech interview
1,500+ real interview questions across engineering, product, design, and data — with worked solutions.
Join the waitlist

Refresh tokens and rotation

Access tokens are short — 15 to 60 minutes is the standard band. Refresh tokens live days, weeks, or months. When the access token expires the client trades the refresh for a new pair:

POST /token
grant_type=refresh_token
&refresh_token=<long-lived>
&client_id=abc

Modern deployments use refresh rotation: every refresh call invalidates the old token and issues a new one. Two refreshes for the same token is a replay signal — the Authorization Server revokes the entire family, forcing the user back through login. Auth0, Okta, and AWS Cognito ship this by default; an OAuth answer that ignores rotation in 2026 sounds dated.

Token Typical lifetime Storage Rotated on use?
Access token 15-60 min Memory or short-lived cookie No
Refresh token (web) 7-30 days httpOnly SameSite=Strict cookie Yes (recommended)
Refresh token (mobile) 30-365 days OS keychain / secure enclave Yes (recommended)
ID token (OIDC) Same as access Memory No

Gotcha: A refresh token in localStorage is one XSS bug away from a full account takeover. The interviewer is listening for "httpOnly cookie" or "secure enclave", not "we store it in localStorage for convenience".

OIDC and JWT in one pass

OIDC (OpenID Connect) is a thin authentication layer on top of OAuth. It adds one thing: an id_token in JWT format, returned alongside the access token, containing claims about the user — sub, email, name, email_verified. This powers every "Continue with Google" button.

The distinction interviewers want to hear: the access token is opaque to you; you hand it to the Resource Server. The id_token is for you, the client — you parse it to know who logged in.

JWT (JSON Web Token) is the encoding format. Three Base64-URL-encoded segments separated by dots:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9   <- header
.
eyJzdWIiOiIxMjMiLCJleHAiOjE3MTYyMjQwfQ <- payload
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV...  <- signature
Segment Contents Notes
Header alg, typ, optional kid kid lets you rotate signing keys
Payload Claims: sub, iss, aud, exp, iat, custom Readable by anyone — not encrypted
Signature HMAC or RSA/EC signature of header+payload Proves integrity, not confidentiality

The standard claims worth memorizing: sub (subject — the user id), iss (issuer — who minted the token), aud (audience — who the token is for), exp (expiration), iat (issued at), nbf (not before), jti (unique id, useful for replay detection).

The single most-asked OIDC follow-up: "what's the difference between aud and azp?" Answer: aud is who the token is for, azp is the authorized party that requested it — they differ when a token is minted for one client and consumed by another.

Flow selection cheat sheet

When the interviewer says "you're designing X, which flow?", run this table in your head.

Scenario Right flow Why
Server-rendered web app with a backend Authorization Code + PKCE Backend safely holds client_secret
SPA in the browser, no backend Authorization Code + PKCE Public client, PKCE is the security
Native mobile app Authorization Code + PKCE Same — client_secret cannot be shipped in a binary
Internal microservice → internal microservice Client Credentials No user; mTLS or signed JWT assertion adds defense-in-depth
CLI tool used by humans Device Authorization Grant No browser on the device; user authorizes on a phone
Smart TV / IoT login Device Authorization Grant Same reasoning — limited input device
Username + password form on your own site Resource Owner Password Credentials Avoid — deprecated in OAuth 2.1; use Authorization Code

If your candidate flow has the Client learn the user's password, you picked wrong.

Common pitfalls

Putting access tokens in URLs. Integrations still pass ?access_token=... because it is convenient for testing. URLs end up in access logs, browser history, referer headers, and screenshot attachments. Tokens belong in the Authorization: Bearer header. The fix is to enforce this in the API gateway with a 400 on any query-string token, so the convenience version never ships to staging.

Shipping client_secret to a public client. Mobile binaries, JavaScript bundles, and desktop apps are all reverse-engineerable in minutes. A client_secret baked into any of them is not a secret. The fix is to register the app as a public client and rely on PKCE. "We obfuscate the secret in the APK" is a security review red flag, not a mitigation.

Skipping JWT signature verification. Reading the payload is one line of code; verifying the signature requires fetching the JWKS, picking the right key by kid, and validating iss, aud, exp, and nbf. Tired engineers skip everything but exp. A forged token with a self-chosen sub walks straight into the system. The fix is a vetted library against a JWKS endpoint, not a hardcoded key.

Access tokens that live for 24 hours. A long-lived access token gives an attacker a long window. The combination that works is a 15-60 minute access token plus a long refresh token with rotation. Access lifetime is non-negotiable; refresh can be tuned per surface — shorter on web, longer on mobile to reduce re-login friction.

Mixing OAuth scopes with internal RBAC. OAuth scopes are coarse — read:photos, write:invoices. Teams try to encode roles, departments, and feature flags into scopes and end up with a 4 KB scope string. Keep scopes as broad capability buckets and put your real permission model in your own authorization service, queried after the token is validated.

Trusting email without email_verified. Google, Microsoft, and Apple all expose unverified addresses in some flows. Key your account model on email alone and an attacker signs up with victim@gmail.com at a sloppy IdP, then claims the existing account at yours. Require email_verified: true and prefer sub as the immutable account key.

If you want to drill systems analyst interview questions like these every day, NAILDD is launching with hundreds of practice questions across exactly this pattern.

FAQ

What is the difference between OAuth 2.0 and OAuth 2.1?

OAuth 2.1 is a consolidation of best practices accumulated since 2012, not a new protocol. It removes the Implicit grant and the Resource Owner Password Credentials grant, requires PKCE for all Authorization Code flows, and tightens redirect_uri matching to exact-string. Describe modern OAuth correctly — PKCE everywhere, no implicit, exact redirect match — and you are effectively describing OAuth 2.1.

Is the Implicit Flow still used anywhere?

It is deprecated and removed in OAuth 2.1, so the right answer in 2026 is no. You will still find it in older SPAs, and you should name the replacement: Authorization Code + PKCE. Implicit was bad because it returned the access token directly in the URL fragment, exposing it to history, extensions, and referer leakage with no replay protection.

When do I pick Client Credentials over Authorization Code?

Use Client Credentials when no human is involved — nightly jobs, microservice-to-microservice calls, partner backend integrations. Use Authorization Code (with PKCE) when a real user is on the other end and the token should represent that user's permissions. Whiteboard heuristic: if the answer to "whose data are we touching?" names a person, pick Authorization Code; otherwise pick Client Credentials.

How do I verify a JWT in production?

Pull the signing keys from the issuer's JWKS endpoint, cache them with sensible TTL, pick the right key by the token's kid header, verify the signature with the algorithm declared in alg, and then check iss, aud, exp, and nbf. Reject tokens where alg is none, reject HS256 tokens if you only ever issue RS256, and never trust the unverified payload. Use a vetted library — jose for Node, pyjwt[crypto] for Python — and configure it strictly.

Is this article official documentation?

No. It is a study guide based on RFC 6749, RFC 7636 (PKCE), the OAuth 2.1 draft, and OpenID Connect Core. Treat it as a primer, not a substitute for the RFCs.