Backend Interview: How Would You Combine OAuth RAR and DPoP for Transaction Authorization?
Prompt and scope
A mobile banking client calls a payment API. The user may approve a single transfer of 500 USD to one beneficiary. The authorization server must show the transaction details during consent, and a copied access token must not be reusable from another client instance. Explain an OAuth 2.0 Rich Authorization Request (RAR) design with Demonstrating Proof of Possession (DPoP), including token issuance, resource-server checks, replay handling, key loss, and verification.
This is a backend security and API-contract question. The exact currency, amount, and beneficiary are interview assumptions, not market facts.
What the interviewer is testing
- Whether you distinguish what the user consented to from what an access token can call.
- Whether you know that RAR carries structured authorization details, while DPoP binds a token to a key and proves possession per request.
- Whether every enforcement point is named: authorization server, token endpoint, resource server, and payment ledger.
- Whether you can contain replay, duplicate submission, key loss, and ambiguous provider responses.
- Whether rollout and tests prove the business invariant instead of only checking JWT syntax.
A weak answer says “put the amount in a scope and sign the JWT.” A strong answer keeps the transaction details structured, binds the issued token to a client key, and makes the final payment state machine authoritative.
Clarifications to ask first
- Is the authorization server also the payment ledger owner? If not, the ledger must re-check the authorization decision at commit time.
- Is one authorization valid for exactly one transfer, or can it authorize a bounded set of transfers? RAR details and token lifetime depend on this boundary.
- Can the mobile client keep a hardware-backed key across reinstall, and how is a lost device revoked?
- Does the resource server receive a DPoP nonce requirement, and what clock-skew window is acceptable?
- What happens after a timeout at the payment provider: can the system query status, or must it record an unknown state?
A 30-second answer framework
“I would put the exact transfer in an authorization_details object instead of inventing a broad scope. The client creates a key pair and sends a DPoP proof when requesting a token; the authorization server binds the token to that public key. The resource server validates the access token, DPoP proof, method, URI, timestamp, nonce, and token-key binding, then passes the authorized transaction identifier to the payment ledger. The ledger accepts it once with an idempotency key and records a terminal state. Replay protection, device revocation, provider timeouts, and audit evidence are separate controls, each tested at its owner.”
Step-by-step solution
1. Model the requested action
RAR defines a structured authorization_details request. The object should contain a registered type, the exact action, amount, currency, destination identifier, and a transaction reference. The authorization server validates schema, account ownership, limits, and policy before presenting consent. It must display the same canonical values that will be used by the ledger; a client-supplied label is not enough.
{
"type": "payment",
"actions": ["transfer"],
"amount": "500.00",
"currency": "USD",
"beneficiary_id": "b_123",
"request_id": "rq_7f2"
}The authorization result should reference a server-owned authorization record. Do not let a resource server reconstruct money, currency, or beneficiary from an untrusted display string.
2. Bind the token to the client key
The client creates a private key in a protected store and sends a DPoP proof JWT to the token endpoint. The proof includes the HTTP method, target URI, issued-at time, a unique proof identifier, and the public key. The authorization server checks the proof and issues a token whose confirmation claim references that key. A bearer token copied without the private key is therefore insufficient for a protected request.
DPoP is application-level sender constraint; it does not replace TLS, secure key storage, authorization policy, or device revocation. The key is an authenticator for the client instance, not proof that the human approved a particular payment.
3. Verify every resource request
The client sends both the access token and a fresh DPoP proof. The resource server checks the signature, token expiry, issuer, audience, confirmation thumbprint, method, URI, clock window, nonce when required, and proof identifier replay cache. It rejects a proof reused for another URI or after its replay window. Access-token binding and authorization-details lookup must be checked together; a valid proof for a different token is not sufficient.
4. Make the ledger the final authority
The resource server creates a payment command containing the authorization record ID, canonical amount and beneficiary digest, client instance ID, and an idempotency key. The ledger compares the command with the approved record in one transaction. It accepts only an unused authorization, writes PENDING or COMMITTED, and rejects a changed amount, destination, expired approval, or second terminal transition. If an external provider times out, record UNKNOWN and query or reconcile before retrying; a timeout does not prove that no charge occurred.
5. Handle recovery and revocation
Store proof identifiers for a bounded replay window and bound the cache by client and issuer. A lost device revokes the key or its authorization record, not just the browser session. Keep audit entries for consent payload hash, displayed values, token key thumbprint, resource decision, ledger transition, and operator action. Redact private keys and raw tokens. A failed rollout can disable the new authorization-details type while retaining old, separately governed flows.
6. Compare alternatives
Broad scopes such as payments.write are simpler but cannot express one amount and one beneficiary; the ledger would need another trusted transaction authorization anyway. Mutual TLS can bind tokens to a certificate and is attractive for controlled server clients, but mobile device certificate lifecycle and network termination are harder. DPoP fits application-managed keys and HTTP clients, but nonce, clock, replay-cache, and key-loss handling become explicit implementation work.
High-quality sample answer
“I would separate four decisions. First, RAR carries a typed payment authorization with the exact amount, currency, beneficiary, and server request ID; the consent screen renders canonical values and the authorization server validates limits. Second, the mobile client uses a protected key and DPoP proofs, and the issued token is bound to that key. Third, the resource server validates the token, proof, URI, time, nonce, and proof replay before loading the server-owned authorization record. Finally, the payment ledger atomically compares that record with the command and allows one idempotent terminal transition. A copied token without the key, a changed beneficiary, a duplicate proof, or a second submission is rejected. A provider timeout becomes UNKNOWN and is reconciled, never blindly retried. I would measure rejected replays, nonce failures, authorization mismatches, duplicate commands, unknown outcomes, and revocation propagation, then canary the new RAR type with a rollback switch.”
Common mistakes
- Put amount and beneficiary in a scope → scopes become unbounded strings and lose typed validation → use a registered authorization-details schema and a server-owned record.
- Treat DPoP as human consent → possession of a key does not prove what the user approved → keep consent, token binding, and ledger authorization separate.
- Validate DPoP only at the gateway → internal callers or alternate routes may bypass the check → enforce the binding at every resource boundary and pass a signed decision ID.
- Retry a provider timeout as a new payment → the first request may have committed → query status, use an idempotency key, and keep an explicit
UNKNOWNstate. - Cache proof IDs forever → memory grows and a policy change becomes hard to reason about → use a bounded replay window tied to the proof lifetime and clock policy.
Follow-ups and responses
What if the attacker steals both the access token and a DPoP proof?
Reject reuse of the proof identifier and enforce its method, URI, time, and nonce constraints. Keep proof lifetimes short and require a fresh proof per request. If the private key is also compromised, revoke the key binding and authorization records; DPoP cannot recover a compromised key by itself.
Why not encode the payment in a JWT scope?
Scope is useful for coarse permission, but a money transfer needs typed fields, validation, display, and a stable server record. A scope string can remain as a broad capability while RAR and the ledger enforce the exact transaction.
What if the resource server and authorization server disagree?
Use a server-owned authorization ID and versioned record. The resource server must fail closed when the record is unavailable or its version is stale for a high-risk action. The ledger rechecks the version in its transaction, so an old cached decision cannot commit a changed payment.
Does DPoP prevent all replay?
No. It reduces replay of a copied token when the attacker lacks the private key and lets the server detect reused proofs. It does not prevent a malicious holder of the key from repeating an allowed command, so ledger idempotency, one-time authorization, rate limits, and device revocation remain necessary.