Deferred Token Response
An OAuth extension for authorization decisions that can't complete synchronously.
Instead of a token or an error, the authorization server hands the client a
deferral_code and a polling interval โ and delivers the real answer later.
OAuth in four terms
DTR is an extension to OAuth 2.1, so a quick refresher on the actors involved. If you already know this, skip to why DTR exists. For the full model, see oauth.net and the OAuth 2.1 draft.
Client
The application requesting access โ a web app, a native app, an agent, a backend service.
Authorization Server (AS)
Issues access tokens after authenticating the client and, where applicable, obtaining resource-owner consent.
Resource Server
Hosts the protected API. It accepts access tokens and enforces the scope and identity they carry.
Access Token
A credential the client presents to the resource server. In every existing grant, the token endpoint issues one immediately, or returns an error.
DTR adds one more idea: the token endpoint's response to a request can be neither of those two things โ it can be not yet.
Why DTR
Existing OAuth grants assume the authorization server can decide synchronously whether to issue an access token. The Authorization Code Grant, Client Credentials Grant, and assertion-based grants like the Identity Assertion JWT Authorization Grant all respond to a token request with either a token or an error โ there's no third option.
Some authorization decisions can't complete synchronously:
Fraud prevention
A sensitive operation triggers manual review by parties other than the resource owner.
ID verification
A submitted physical credential needs human inspection, which can take hours.
Autonomous agent authorization
An agent requests access beyond what was provisioned at enrollment and needs out-of-band approval.
Complex authorization
Enterprise governance workflows route the decision through approvers other than the resource owner.
Today, the authorization server's only option in each case is to return an error and leave the client with no way to find out when โ or whether โ the request will eventually be approved. DTR gives it a real third response: a deferred one.
It's deliberately not the Device Authorization Grant. Device flow deferral is initiated by the client, and assumes the same end-user who started the flow is the one who'll approve it. DTR deferral is initiated by the authorization server, dynamically, based on policy or risk analysis โ and makes no assumption about who (or what, or whether anyone) completes the approval.
The basic flow
DTR runs entirely through the originating grant's existing endpoints. A client that opts
in gets back a deferral_code in place of a token, polls until the request
resolves, and then gets the token response it would have gotten synchronously.
That's the whole shape of the protocol. Everything else โ what triggers a defer decision,
what interaction_required means, how callbacks work, how cancellation works โ
is detail on top of this loop. The reference section below walks through it step by step.
Step by step
-
Opting in:
completion_mode=deferredDTR is opt-in from the client's side. An authorization server must not defer a request unless the client has signaled it can handle one โ an authorization server that deferred responses to clients that weren't expecting them would break every existing OAuth client on the internet.
The signal is the
completion_moderequest parameter: a space-separated list of values sent on the token request. This draft defines one value,deferred. Include it and you're telling the authorization server you can handle a deferred response in place of an immediate token or error; omit it and the server must handle your request synchronously, exactly as it does today.POST /token HTTP/1.1 Host: server.example.com Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=SplxlOBeZQQYbYS6WxSbIA &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb &completion_mode=deferredSending it doesn't entitle the client to a deferred response โ the server decides, per-request, whether to defer at all. And a grant with a preceding step (like the authorization endpoint) can carry the same parameter early, as an advance hint, so the server can pick a deferral-aware path before the token request even arrives.
-
The deferred response
When the server elects to defer, it returns an OAuth error response โ HTTP 400, error code
authorization_pendingโ carrying adeferral_codeinstead of a token. It's not a token response: no access is granted yet.HTTP/1.1 400 Bad Request Content-Type: application/json Cache-Control: no-store { "error": "authorization_pending", "deferral_code": "LRGidcSAeVs_xYpEDt5pNx0A0TBr7qPHbh_Se6pfdf4", "expires_in": 10800, "interval": 60 }Parameter Meaning deferral_codeOpaque, sender-constrained identifier for this pending request. Required for every subsequent poll. expires_inSeconds until the deferral code itself expires โ not the eventual access token's lifetime. intervalMinimum seconds the client must wait between polls. Given once, up front; not repeated on later poll responses. interaction_uriOptional here (required once the server returns interaction_required, below) โ a URI where an external party can act on the request. -
Polling
The client redeems the deferral code with a new grant type reserved for this purpose, at no faster than the given
interval:POST /token HTTP/1.1 Host: server.example.com Content-Type: application/x-www-form-urlencoded grant_type=urn:ietf:params:oauth:grant-type:deferred &deferral_code=LRGidcSAeVs_xYpEDt5pNx0A0TBr7qPHbh_Se6pfdf4Each poll returns either the final token response, or one of these errors:
-
authorization_pendingStill pending, no external interaction outstanding. Keep polling atinterval. -
interaction_requiredPending, and blocked on external interaction. The response includes aninteraction_uriโ direct the user (or whichever party needs to act) there, and keep polling. -
interaction_pendingThe interaction has been started (the user has reachedinteraction_uri, or the server-side process has begun) but hasn't finished yet. Keep polling. Proposed in PR #68, approved, pending merge. -
slow_downPolling faster thanintervalallows. Back off by at least 5 seconds and keep going. -
expired_tokenThe deferral code's lifetime ran out. Terminal โ start a new flow if you still need access. -
access_deniedThe request was denied, or the deferral code was revoked. Terminal.
authorization_pending,interaction_required, and (pending the PR above)interaction_pendingare the only non-terminal states โ a request can move between them in either direction while it's pending. -
-
Success: tokens returned
Once the request resolves, polling returns the same token response the originating grant would have returned synchronously โ nothing DTR-specific about its shape.
HTTP/1.1 200 OK Content-Type: application/json Cache-Control: no-store { "access_token": "SlAV32hkKG", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "8xLOxBtZp8" }The deferral code is single-use: once it's been redeemed for a token response, any further poll with the same code gets
invalid_grant.