DTR OAuth WG Draft
Try the demo โ†’

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.

๐Ÿšง IETF OAuth Working Group draft, accepted for adoption. Text on this page tracks the working draft and may change before the first WG submission.
Background

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.

Motivation

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.

Mechanism

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.

Client Authorization Server POST /token completion_mode=deferred 400 authorization_pending deferral_code, expires_in, interval client polls no faster than "interval", repeating while pending POST /token grant_type=deferred, deferral_code 400 authorization_pending / interaction_required / โ€ฆ POST /token grant_type=deferred, deferral_code 200 OK access_token, token_type, expires_in, โ€ฆ

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.


Reference

Step by step

  1. Opting in: completion_mode=deferred

    DTR 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_mode request 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=deferred

    Sending 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.

  2. The deferred response

    When the server elects to defer, it returns an OAuth error response โ€” HTTP 400, error code authorization_pending โ€” carrying a deferral_code instead 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
    }
    ParameterMeaning
    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.
  3. 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_Se6pfdf4

    Each poll returns either the final token response, or one of these errors:

    • authorization_pending Still pending, no external interaction outstanding. Keep polling at interval.
    • interaction_required Pending, and blocked on external interaction. The response includes an interaction_uri โ€” direct the user (or whichever party needs to act) there, and keep polling.
    • interaction_pending The interaction has been started (the user has reached interaction_uri, or the server-side process has begun) but hasn't finished yet. Keep polling. Proposed in PR #68, approved, pending merge.
    • slow_down Polling faster than interval allows. Back off by at least 5 seconds and keep going.
    • expired_token The deferral code's lifetime ran out. Terminal โ€” start a new flow if you still need access.
    • access_denied The request was denied, or the deferral code was revoked. Terminal.

    authorization_pending, interaction_required, and (pending the PR above) interaction_pending are the only non-terminal states โ€” a request can move between them in either direction while it's pending.

  4. 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.

Not covered here: callback notifications (an alternative to polling where the server pings the client's own endpoint when a request resolves), and cancellation. Both are in the full draft.