Representative interview topic

Backend interview: How would you design an OAuth 2.0 step-up authentication challenge?

BackendHard
Offer.cc Editorial TeamPublished Updated

Question

Your API permits low-risk reads, but transfers and payout changes require stronger authentication. Design an OAuth step-up challenge covering errors, authentication context, retries, token validation, and downgrade boundaries.

Prompt and context

A resource server accepts a normal access token for reads, but transfers, payout-account changes, and sensitive exports require a stronger authentication level. When the client lacks that level, it should receive an actionable challenge rather than a vague 403. Design the step-up flow among the resource server, authorization server, and client using RFC 9470.

RFC 9470 defines the insufficient_user_authentication error for a Bearer challenge and uses acr_values or related authentication context to express the required level. The interview tests the complete loop across API errors, token claims, reauthorization, idempotent retries, and downgrade boundaries.

What the interviewer is testing

  • Distinguishing authorization failure, insufficient user authentication, and token expiry.
  • Expressing required authentication context in WWW-Authenticate without exposing risk internals.
  • Letting a client reauthorize from a challenge while preventing loops and downgrades.
  • Verifying issuer, audience, scope, acr, amr, and time claims at the resource server.
  • Handling retries, concurrency, audit, rollback, and user experience for high-risk writes.

Clarifying questions

  1. Which operations require a higher level, and is the requirement an acr, an amr, or a transaction confirmation?
  2. Are access tokens locally validated JWTs or introspected? Can the resource server see authentication context?
  3. Is the client a web, mobile, or server application? Are PKCE, prompt, and max_age available?
  4. Should a challenge be single-use and bound to amount, payee, and a nonce?
  5. If the authorization service is unavailable, which reads may continue and which writes must fail closed?

30-second answer

Validate the token's issuer, audience, signature, expiry, and scope first. If scope is sufficient but authentication context is not, return 401 with a WWW-Authenticate Bearer challenge using insufficient_user_authentication and the required acr_values and resource. The client preserves intent and state, reauthenticates, receives a token bound to the same audience, scope, and context, and retries once. Give challenges short TTLs, bounded retries, and transaction binding; never silently downgrade high-risk operations.

Deep-dive answer

1. Define authentication and resource policy

Map operations to minimum authentication policies: a basic level for reads, phishing-resistant authentication for payout changes, and transaction confirmation for transfers. Evaluate endpoint, method, tenant, amount, and risk signals rather than equating every write with one fixed level.

The authorization server defines registered acr values and acceptable authentication methods. The resource server accepts only registered values; a client cannot self-assert a higher level. amr is evidence and does not replace policy evaluation of acr.

2. Design the challenge response

When a valid token lacks the required context, return 401 and a Bearer challenge with error="insufficient_user_authentication". It may include required acr_values, a resource identifier, and an error URI, but must not reveal account numbers, risk scores, or internal rules.

Handle missing or unverifiable context as insufficient. Use distinct errors for expired tokens, untrusted issuer, wrong audience, and missing scope; do not disguise every failure as step-up.

3. Let the client reauthorize

The client parses the challenge and binds the original target, required acr_values, resource, and PKCE to a new authorization request. Web clients use state and OIDC clients use nonce; mobile clients should not treat an unverified challenge as an executable authorization URL.

Reauthorization still requires authorization-server authentication and consent. prompt, max_age, or an authentication policy is a request; the server writes the achieved acr into the token based on actual authentication. The client cannot mark a token upgraded locally.

4. Verify the new token and original intent

Verify signature, issuer, audience, scope, exp, iat, acr, and required amr on the new token. With introspection, require a trusted authorization-server response with sufficient context. Scope alone is insufficient because permission and authentication strength are separate dimensions.

Bind a challenge nonce, transaction digest, or authorization-request ID to server state for high-risk actions so a higher-authentication token cannot be moved to another transaction. Keep the token audience exact for the target API.

5. Prevent loops, replay, and downgrade

Give each challenge a short TTL and unique ID, recording request digest, tenant, resource, required level, and retry count. Allow one or a small bounded number of retries; consume a successful challenge and terminate failed or expired ones.

Reject lower acr_values, resource or audience substitution, and fallback to an ordinary token after step-up timeout. Use idempotency keys for transfers so authentication retries cannot duplicate side effects.

6. Handle availability and error boundaries

If the authorization service or introspection is unavailable, low-risk reads may use a short cache; writes, payments, and permission changes fail closed by default. Challenge errors should not reveal authentication methods or account state. Logs retain challenge ID, issuer, result, and latency.

Track insufficient-context rate, challenge completion, loops, expiry, replay, PKCE failures, denial by acr, and duplicate business effects. Segment alerts by resource and tenant to separate policy errors from attacks.

7. Roll out and roll back safely

Enable step-up on one API and tenant first, comparing 401s, completion, latency, support feedback, and high-risk denial. Clients that do not understand challenges receive documented errors and SDK guidance; ordinary tokens are not silently accepted for everyone.

Rollback disables only not-yet-enabled resource policies and preserves challenge state and audit for active transactions. If context leaks, side effects duplicate, or loops appear, pause rollout, revoke the policy, and recheck token binding.

Model answer

I would define a minimum authentication level for each operation. After validating a token, the resource server returns 401 with insufficient_user_authentication and minimal acr_values, resource, and error URI when scope is sufficient but acr or amr is not. The client preserves intent and uses state, nonce, and PKCE to reauthorize; the authorization server issues a token whose context reflects actual authentication.

The resource server verifies issuer, audience, scope, time, acr, and required amr again, binding a challenge ID or transaction digest to server state. Challenges are short-lived, one-time, and retry-bounded; lower levels, new audiences, and unconditional fallback are rejected. High-risk writes fail closed during authentication outages, and transfers use idempotency keys.

Common mistakes

  • Returning a generic 403 or 401 instead of an actionable insufficient_user_authentication challenge.
  • Checking scope but not issuer, audience, acr, amr, and time claims.
  • Letting the client self-assert a higher acr, or lower it after a challenge.
  • Treating a challenge as an unverified authorization URL and skipping state, nonce, or PKCE.
  • Retrying forever or omitting transaction binding, allowing token reuse across operations.
  • Silently downgrading transfers or permission changes when authentication is unavailable.
  • Logging accounts, risk scores, or complete tokens.

Follow-up questions and answers

Why return 401 when scope is sufficient but acr is not?

Scope says what the token may access; acr says how the user authenticated. A resource can require both, so the client needs a new authorization step.

What belongs in a challenge?

Only the required authentication level, resource identifier, error URI, and short-lived challenge identifier needed for client action. Keep account, amount, internal risk, and method details server-side.

Can the client call the token endpoint directly to upgrade?

No. It must follow the challenge through authorization-server authentication and consent. The server, not the client, asserts the achieved acr in the token.

How do you stop an upgraded token being used for another transfer?

Bind audience, resource, challenge ID, or transaction digest to one-time server state, and use an idempotency key for the transfer.

What if introspection is temporarily unavailable?

Classify by resource risk. Short cached evidence may serve ordinary reads; payments and permission changes fail closed when context cannot be confirmed.

How do you support old clients that do not understand challenges?

Publish a documented error and SDK migration, then enable per tenant. High-risk operations keep their requirement until migration is complete.

Public sources

Related questions