skip to content

OAuth2 & OIDC Integration

Consuming an identity provider from a server you own: the login callback, machine tokens, identity carried across hops, signing out everywhere. Interviewers ask which parts you still have to write.

on this pageshow

questions

18

A server-side relying party redirects a depot supervisor to the fleet operator's identity provider — what must it keep until the callback arrives?

level: juniorimportance: must knowfreq 72%

answer

  1. two requests, minutes apart
  2. the callback brings back very little
  3. state is the key, not the data
  4. random values cannot be recomputed
  5. read once, then delete

basics

~20 s

A relying party parks one short-lived record per sign-in attempt, holding the value it sent as state, the nonce, the code_verifier, which provider was used, and the page the user was heading to. The callback request itself brings back almost none of that.

solid answer

~40 s

An authorization-code login is two unrelated HTTP requests minutes apart, and the browser only brings back what the provider echoes: the authorization `code`, the `state` value you sent, and from some providers an `iss`. Everything else has to be waiting on your side. Keep it as one record per sign-in attempt, keyed by the value that travels in `state`: the `nonce` you generated, the `code_verifier` the token request will have to present, which provider the request went to, the destination the user was originally heading to, and a creation timestamp. The callback looks the record up, consumes it, exchanges the code, runs the checks the specification requires, and hands the resulting subject to your session layer. Give it a TTL in minutes and delete it on read.

code

http · 3 lines
http
GET /auth/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=Qm9vdC1sb2FkZXItMTc HTTP/1.1
Host: fleet.example.com
Accept: text/html

go deeper

for a junior

Recall the list: the value sent as state, the nonce, the code_verifier, which provider, and where the user was heading. Say plainly that the callback brings back only a code and the state value.

for a middle

Explain why each item cannot be recovered at the callback — random values cannot be recomputed and context is not echoed — and walk the callback's order of work from lookup to handing a subject on.

for a senior

Show the operational side: a TTL measured in minutes, delete-on-read so a replayed callback finds nothing, and an expired record rendering a restart page rather than an error page.

for a principal

Frame the record as the whole state of a half-finished login. Where it is kept decides how the service may be deployed and what a failed sign-in costs, so it is an architectural choice, not a detail.

## An authorization-code login is two requests, not one A server-side relying party runs a federated login as two separate HTTP exchanges. In the first, the dashboard builds an authorization request and redirects the browser away to the fleet operator's identity provider. In the second — seconds or minutes later, after the supervisor has typed a password and possibly answered a second factor — the browser arrives back at the dashboard's callback endpoint. Nothing in HTTP connects the two. The callback is an ordinary request from a browser that has just been somewhere else. So "what must the server keep" is really "what does the callback fail to bring". ## What the browser actually brings back The provider echoes only what it is supposed to echo: - the authorization **`code`**, single-use and short-lived; - the **`state`** value your authorization request carried, returned unchanged; - from providers that implement it, an **`iss`** value naming which authorization server answered. That is the payload. The provider is not holding your application's memory for you, and the browser is not a place to keep it. Everything the second half of the login needs has to be waiting on your side, findable from the one value you know will come back. ## The record, as one object Hold it as a single record per sign-in attempt rather than as four loose values in four places — one write when the authorization request is built, one read-and-delete when the callback lands: - **the value you sent as `state`** — this is the record's key, not a field inside it; - **the `nonce`** you generated for the authorization request, kept so the ID-token check at the callback has something to compare against; what that comparison proves is the specification's subject, not yours; - **the `code_verifier`**, because the token request has to present the original random value whose derived `code_challenge` went out earlier, and nothing in the callback carries it; - **which provider the request went to**, the moment the dashboard offers more than one sign-in button; - **the destination** the supervisor was originally heading to, so the login lands them on the vehicle list they clicked rather than the home page; - **a creation timestamp**, so the record can expire on its own. Everything in that list is either random, and therefore unrecoverable, or contextual, and therefore unknowable from the callback. That is the test for what belongs in the record. ## What the callback endpoint does, in order 1. Read the `state` query parameter and look up the record by it. 2. If there is no record — absent, expired, already consumed — stop and render a page saying the sign-in expired, offering to start again. This is a normal outcome, not a server error. 3. Consume the record: delete it, atomically, before any further work, so a concurrent or replayed callback finds nothing. 4. Exchange the `code` at the token endpoint, presenting the `code_verifier` from the record. 5. Run the checks the specification requires on what comes back, using the `nonce` you kept. 6. Hand the resulting subject to your own session layer, issue your own session, and redirect to the destination from the record. The relying party's work ends at step 6. What the session layer then does with that subject — where the resulting credential rests in the browser, how long the session lives, whether a local account is created on first sight — is a separate concern with a separate owner. ## Lifetime and single use The record's TTL is **minutes, not hours**. It must be long enough for a realistic authentication at the provider, including a second factor and the occasional password reset; ten to fifteen minutes is defensible and an hour is not. A long TTL buys nothing and leaves a large pile of half-finished logins sitting in a store. Single use matters as much as expiry. A callback can arrive twice for entirely ordinary reasons: a double-clicked link, a refresh, a back button, an over-eager link prefetcher. Delete-on-read makes the second arrival find nothing, which is right — the first attempt either issued a session or failed, and either way the second has nothing to do. If a session was already issued, notice that and send the supervisor onward rather than showing an error. The token endpoint is a second line of defence here: an authorization code redeemed twice comes back as `invalid_grant`, not as a second set of tokens. ## Where this goes wrong first - Keeping the record in a process-local map, which is wrong the moment there is a second instance and is emptied by every deploy. - Trying to derive the `code_verifier` from something deterministic instead of storing the random value. - Pushing the post-login destination into the callback URL instead of into the record. - Writing records and never expiring them, so the store grows for the life of the service.

  • Why must the code_verifier be stored rather than derived again at the callback?
    It is fresh random data generated per authorization request. Nothing in the callback carries it and nothing deterministic reproduces it, while the token request has to present that exact original value. Deriving it from something stable — a session id, a user id, a hash of the time — would make it predictable, which defeats the reason it is random. So it is stored, and it expires with the rest of the record.
  • The same callback arrives twice within a second. What happens?
    The first read consumes the record; the second finds nothing. Render the restart page, or, if the first attempt already issued a session, notice that and send the user on to their destination instead of showing an error. The token endpoint backs this up: an authorization code redeemed twice comes back as `invalid_grant` rather than as a second set of tokens.
  • What does the callback endpoint hand on once the response checks out?
    A subject — the provider's `iss` and `sub` identifiers plus whatever claims you trust — handed to your own session layer, which issues your session. Creating a local account on first sight, mapping claims to roles, joining this identity to an existing account, and deciding where the resulting credential rests in the browser are all separate concerns owned elsewhere.

saying these in an interview costs you the question

  • Thinks the provider returns the nonce and code_verifier with the callback
  • Recomputes the code_verifier instead of storing the random value
  • Keeps pending-login records in a process-local map
  • Gives the record an hour-long TTL, or no TTL at all
  • Returns a server error when the record has expired
  • Puts the post-login destination into the callback URL
open as a page

A scheduled meter-polling service holds its own machine token — why cache it instead of acquiring one per outbound call?

level: juniorimportance: must knowfreq 52%

basics

~20 s

A machine token stays valid for the whole expires_in window the authorization server granted, so one acquisition serves thousands of polls. Acquiring per call adds a round trip to every request and hammers a shared, rate-limited endpoint for nothing.

open as a page

A user disconnects your meal planner at their grocery-list provider — how does your server find out?

level: juniorimportance: must knowfreq 58%

basics

~20 s

No notification arrives. The next refresh of that user's stored grant is refused with invalid_grant, or an API call answers 401, and that refusal is terminal: mark the row dead and stop the work rather than retrying.

open as a page

At federated login, what must your server record so that a later logout notice naming a provider session identifier maps onto local sessions?

level: middleimportance: must knowfreq 45%

basics

~20 s

Record the issuer, the subject and, when the provider issues one, the provider session identifier on every local session row you create at login. Without that index an arriving logout notice names nothing you hold, and your endpoint can only discard it.

open as a page

Your service can run its own OAuth 2.0 authorization server or rent a managed one — what does each choice leave your team to operate?

level: middleimportance: must knowfreq 52%

basics

~20 s

Renting moves the login path's uptime, its abuse handling and most of its key custody onto someone else; running your own leaves you all three plus the on-call rotation behind them. Neither choice moves your obligation to show who consented to what.

open as a page

What must the cache key for a service's own machine token contain, and what breaks when one global slot is used?

level: middleimportance: must knowfreq 45%

basics

~20 s

The key must identify what the token is good for: the client registration it was issued to, the normalised scope set requested, and the callee it was requested for. One global slot makes two different calls overwrite each other and sends each a token the other's callee will refuse.

open as a page

A meal planner stores one third-party API grant row per user and provider — why is the granted scope set part of that row's identity?

level: middleimportance: must knowfreq 55%

basics

~20 s

Read access to a list is not write access to it, and a refresh request may not ask for scope that was never granted, so user plus provider plus the granted scope set identifies the authorization you actually hold.

open as a page

Your relying party runs on several instances: sign-in starts on one and the callback lands on another — where does the pending-login record live?

level: seniorimportance: must knowfreq 58%

basics

~20 s

Park the record where any instance can reach it: a shared short-TTL store keyed by the value sent in state, or a signed cookie the browser carries back. An instance's own memory is correct only behind routing affinity, and a deploy drops every in-flight login.

open as a page

Exchanging the caller's token once at the edge or again at every hop: what does each cost on a four-service donation chain?

level: seniorimportance: must knowfreq 46%

basics

~20 s

Exchanging once at the edge costs one round trip but leaves the inward token spendable at every service behind it. Exchanging again at each hop narrows the token to its callee and adds a round trip and a failure point per hop.

open as a page

In the phantom-token pattern, why does only the edge trade the opaque token for an inward JWT, and how long should that JWT live?

level: middleimportance: should knowfreq 34%

basics

~20 s

The edge is the one place an outside-facing opaque token arrives and can be resolved with a single issuer call, so the trade pays there and nowhere else. The inward JWT should live roughly one request, not one session.

open as a page

A back-channel logout notice from the identity provider is delivered at least once — what must your handler do so a repeat is harmless?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Write the handler to converge on an end state rather than to process an event: resolve the notice to a set of local sessions, delete conditionally, and answer success when there is nothing left to delete. A repeat then changes nothing.

open as a page

Renting an authorization server bills per identity: how does that compare with running your own when the population is 40,000 for ten months and 4 million for two?

level: seniorimportance: should knowfreq 38%

basics

~20 s

A per-identity bill breathes with the population and concentrates almost all of a seasonal year's cost into two months. A self-run issuer is mostly fixed — capacity floor, datastore, on-call rotation — and the fixed part does not shrink in the quiet months.

open as a page

Your machine-token cache is cold on every site controller after a deploy and all wake on the same half-hour boundary — how do you avoid a burst at the authorization server?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Coalesce concurrent misses so one acquisition per cache key serves every waiting caller, refresh ahead of expiry in the background while the current token is still served, jitter the refresh point and the wake boundary so instances do not re-synchronise, and warm the cache before an instance is declared ready.

open as a page

Half your users' stored grocery-list grants expire within the same hour — do you refresh them on a background sweep or lazily at first use?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Refresh at the moment of use for anything a user is waiting on, and sweep ahead only for the grants background work needs; drive the sweep off a jittered due time so a cohort authorized in one hour cannot stampede the provider.

open as a page

Two years into a rented authorization server, what does moving every user onto an issuer you run yourself actually cost you?

level: principalimportance: should knowfreq 30%

basics

~20 s

Identities export; proofs usually do not. Password hashes and second-factor secrets commonly cannot come across, every local row keyed on the old issuer's subject identifier must be re-keyed, and each standing user grant needs a fresh, user-visible consent.

open as a page

Your machine caller's client secret must be replaced at the authorization server — how do you sequence that without losing acquisition?

level: principalimportance: should knowfreq 30%

basics

~20 s

Register the new credential alongside the old one so both authenticate the same client, roll the deployed value instance by instance as an ordinary deploy, confirm from acquisition evidence that every instance now uses the new credential, then remove the old one. Never change the registration and the fleet in one step.

open as a page

Exchanging identity at every hop puts the authorization server on the critical path of every internal call — at what chain depth does that stop being worth it?

level: principalimportance: should knowfreq 27%

basics

~20 s

There is no fixed depth. It stops being worth it when the added exchanges and the availability you put in series exceed the reach you actually remove, so narrow at boundaries whose blast radius genuinely differs rather than at every hop.

open as a page