From 4e610d7059920c6887f5a855c22cb194c37a0e16 Mon Sep 17 00:00:00 2001 From: Karl McGuinness Date: Tue, 23 Jun 2026 09:47:48 -0700 Subject: [PATCH 1/6] Add AuthZEN Access Request OAuth Profile (AROP) draft New companion profile defining the OAuth token-issuance completion mode that the Access Request and Approval Profile (ARAP) defers to a downstream spec. A client requests a token (completion_mode=deferred, RAR authorization_details); the AS evaluates it as an AuthZEN Access Evaluation; on a denied-but-requestable decision it runs an ARAP access request and returns a Deferred Token Response (draft-gerber-oauth-deferred-token-response) with a deferral_code; the client polls the token endpoint; on approval the AS re-evaluates and issues an access token carrying the approved authorization, bounded by the approval expiry. Adds no new OAuth grant type or parameters: composes DTR (transport), RAR (requested and approved authorization), and ARAP (approval lifecycle). States the issuance-time authority shift honestly (decision at issuance, bounded by token lifetime) and keeps the decision behind the AS so the token carries the grant, not an AuthZEN decision object. --- .../authzen-access-request-oauth/Makefile | 21 + ...uthzen-access-request-oauth-profile-1_0.md | 457 ++++++++++++++++++ 2 files changed, 478 insertions(+) create mode 100644 profiles/authzen-access-request-oauth/Makefile create mode 100644 profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md diff --git a/profiles/authzen-access-request-oauth/Makefile b/profiles/authzen-access-request-oauth/Makefile new file mode 100644 index 0000000..6235d02 --- /dev/null +++ b/profiles/authzen-access-request-oauth/Makefile @@ -0,0 +1,21 @@ +SOURCES ?= $(wildcard *.md) +HTML = $(SOURCES:.md=.html) +TEXT = $(SOURCES:.md=.txt) + +all: html text +html: $(HTML) +text: $(TEXT) + +%.html: %.xml + xml2rfc --html $^ + +%.txt: %.xml + xml2rfc $^ + +%.xml: %.md + kramdown-rfc2629 > $@ $^ + +clean: + rm -f *.xml *.txt *.html + +.PHONY: all html text clean diff --git a/profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md b/profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md new file mode 100644 index 0000000..d72d366 --- /dev/null +++ b/profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md @@ -0,0 +1,457 @@ +--- +title: "AuthZEN Access Request OAuth Profile - Draft 1" +abbrev: "AROP" +category: std + +docname: authzen-access-request-oauth-profile-1_0 +submissiontype: IETF +workgroup: OpenID AuthZEN +consensus: true +v: 3 +keyword: + - authorization + - OAuth + - access token + - deferred token response + - rich authorization requests + - AI agent + - access request + - approval workflow + - just-in-time access + - fine-grained authorization + +venue: +# group: "OpenID AuthZEN Working Group" +# type: "Working Group" +# mail: "openid-specs-authzen@lists.openid.net" +# arch: "https://lists.openid.net/pipermail/openid-specs-authzen/" + +author: + - + name: Karl McGuinness + org: Independent + email: public@karlmcguinness.com + +normative: + I-D.ietf-oauth-v2-1: + RFC9396: + RFC9449: + RFC8705: + RFC8693: + RFC7009: + RFC8707: + I-D.gerber-oauth-deferred-token-response: + AuthZEN: + title: "Authorization API 1.0" + target: "https://openid.github.io/authzen/" + author: + - + ins: O. Gazitt + name: Omri Gazitt + - + ins: D. Brossard + name: David Brossard + - + ins: A. Tulshibagwale + name: Atul Tulshibagwale + date: 2026-04-29 + ARAP: + title: "AuthZEN Access Request and Approval Profile" + target: "https://openid.github.io/authzen/authzen-access-request-approval-profile-1_0.html" + author: + - + ins: K. McGuinness + name: Karl McGuinness + date: 2026 + +--- abstract + +This specification defines an OAuth binding for the AuthZEN Access Request and Approval Profile in which an approved access request completes as an issued OAuth access token. When an Authorization Server evaluates a token request as an AuthZEN Access Evaluation and the decision is denied but requestable, the Authorization Server resolves it through an access request and completes by issuing an access token that carries the approved authorization. The asynchronous wait is carried by the OAuth Deferred Token Response: the Authorization Server returns a deferral code, and the client polls the token endpoint until a token is issued or the request is denied. The binding adds no new OAuth grant type or protocol parameter; it composes the Deferred Token Response, Rich Authorization Requests, and the Access Request and Approval Profile. + +--- middle + +# Introduction + +The AuthZEN Access Request and Approval Profile {{ARAP}} defines how a denied but requestable authorization decision is resolved out of band and re-evaluated. It defines a single completion mode, `reevaluate`, in which the Policy Decision Point (PDP) remains authoritative at enforcement time, and it explicitly leaves completion modes that bind approval to a specific issuance flow, such as OAuth token issuance where the issued token is itself the decision representation, to a downstream profile. + +This specification is that downstream profile for OAuth 2.1 {{I-D.ietf-oauth-v2-1}}. It defines a **token issuance completion mode**: a client requests a token, the Authorization Server (AS) evaluates the request as an AuthZEN Access Evaluation {{AuthZEN}}, and when the decision is denied but requestable the AS resolves it through an {{ARAP}} access request and completes by issuing an access token that carries the approved authorization. + +The asynchronous wait is carried by the OAuth Deferred Token Response {{I-D.gerber-oauth-deferred-token-response}} (DTR): rather than returning a token or an error, the AS returns a `deferral_code`, and the client polls the token endpoint until the token is issued or the request is denied. + +The binding adds no new OAuth grant type and no new protocol parameters. It rides on DTR (`completion_mode=deferred` and the deferred grant type) over any originating grant, including Token Exchange {{RFC8693}}, and uses Rich Authorization Requests {{RFC9396}} (RAR) to carry both the requested and the approved authorization. + +# Requirements Notation and Conventions + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 {{!RFC2119}} {{!RFC8174}} when, and only when, they appear in all capitals, as shown here. + +# Design Goals + +- **OAuth-native completion.** In this mode the issued access token is the decision representation. Authorization is exercised by presenting the token to a resource server, not by carrying an AuthZEN decision object on the wire. +- **Compose, do not reinvent.** The asynchronous transport is DTR, the fine-grained request and grant are RAR, and the approval lifecycle is {{ARAP}}. This profile only binds them; it defines no new endpoints, grant types, or parameters. +- **The decision stays behind the AS.** The token request carries a RAR request, the AS maps it to an AuthZEN evaluation internally, and the issued token carries the approved grant. The token never carries an AuthZEN request or decision object (see {{relationship}}). +- **Least privilege.** The issued token's granted authorization (`authorization_details`, and `scope` if used) reflects the approved access and MUST NOT broaden the originating request. The token SHOULD be audience-restricted to the intended resource using resource indicators {{RFC8707}}, so a narrowly approved token is not presentable at an unintended resource server. +- **Authority model: an issuance-time decision, bounded by token lifetime.** {{ARAP}}'s base model keeps the PDP authoritative at enforcement through per-call re-evaluation. A token issuance completion mode necessarily moves the authoritative decision to issuance time: the AS evaluates, issues a bearer token, and a resource server later validates that token, possibly offline. This profile therefore trades per-call re-evaluation for an issuance-time decision bounded by the token's lifetime. Deployments preserve currency with short token lifetimes and token introspection, the issued access token's lifetime MUST NOT exceed the approval expiry the Access Request Service recorded, and the approval bounds the whole credential rather than a single token (see {{completion}}). + +# Terminology + +This profile uses the roles of {{AuthZEN}} and {{ARAP}}, mapped onto OAuth: + +Authorization Server (AS): +: The OAuth Authorization Server. In this profile the AS acts as (or fronts) the AuthZEN PDP for the token request and is the token issuer. Any OAuth token-issuing endpoint can play this role, so approval is gated wherever the token is requested: a single AS, the identity provider (IdP) or Resource Authorization Server (RAS) in an ID-JAG {{?I-D.ietf-oauth-identity-assertion-authz-grant}} chain, or a resource server that issues its own tokens. + +Access Request Service (ARS): +: The {{ARAP}} role that runs the approval workflow. The AS submits the access request to it and tracks resolution. It is internal to the AS deployment and not exposed to the client. + +Client: +: The OAuth client (an agent, or a Policy Enforcement Point acting for one) that makes the token request and polls for completion. + +Resource Server (RS): +: The server that accepts the issued access token and enforces the authorization it carries. In this profile the RS is the enforcement point: it validates the token, possibly offline, rather than re-evaluating against a PDP per call. + +This profile also uses `completion_mode`, `deferral_code`, and the deferred grant type from {{I-D.gerber-oauth-deferred-token-response}}, and `authorization_details` from {{RFC9396}}. + +# Protocol Overview + +~~~ ascii-art ++--------+ +---------------------+ +------------------+ +| Client | | Authorization Server| | Access Request | +| | | (PDP + issuer) | | Service (ARS) | ++---+----+ +----------+----------+ +--------+---------+ + | | | + | 1. token request | | + | completion_mode=deferred | | + | authorization_details (RAR) | | + |---------------------------------------->| | + | | evaluate (AuthZEN); | + | | denied but requestable | + | |--------------------------->| + | | submit access request | + | 2. authorization_pending | | + | deferral_code, expires_in, interval | | + |<----------------------------------------| | + | | ... out-of-band approval | + | 3. poll (grant_type=deferred) | | + |---------------------------------------->| | + | authorization_pending | | + |<----------------------------------------| | + | | approved | + | |<---------------------------| + | | re-evaluate with approval; | + | | issue token | + | 4. access_token (+ authorization_details) | + |<----------------------------------------| | +~~~ + +# Discovery + +An AS that supports the Deferred Token Response advertises `deferred_token_response_supported` in its authorization server metadata ({{I-D.gerber-oauth-deferred-token-response}}). In a multi-issuer topology, each token-issuing party (a single AS, the IdP and the RAS in an ID-JAG chain, or a resource server that issues its own tokens) advertises this independently in its own metadata. A client participates by including `completion_mode=deferred` on its token request and being prepared for any of three outcomes: a synchronous token response, a deferred response (`authorization_pending` with a `deferral_code`), or an error. This profile defines no additional discovery metadata. Whether a given token request is evaluated as an AuthZEN Access Evaluation and may be deferred for approval is an AS policy decision and is not separately advertised. + +# Requesting and Completing a Deferred Token + +## Initial token request + +The client makes an ordinary token request and signals deferred completion with `completion_mode=deferred` ({{I-D.gerber-oauth-deferred-token-response}}). The requested authorization is expressed with `authorization_details` ({{RFC9396}}), with `scope`, or with both. The AS maps the request to an AuthZEN Access Evaluation and evaluates it against its PDP, as described in {{authzen-integration}}. The AS resolves as follows: + +- **Allow.** The AS returns the originating grant's normal token response synchronously. +- **Deny, not requestable.** The AS returns a synchronous token-endpoint error: `invalid_authorization_details` ({{RFC9396}}) when the denial concerns the requested `authorization_details`, `invalid_scope` when it concerns requested `scope`, and otherwise `invalid_grant`. `access_denied`, `authorization_pending`, and the other Deferred Token Response error codes are reserved for the deferred path and MUST NOT be returned here. +- **Deny, requestable, client offered `completion_mode=deferred`.** The AS MUST NOT issue a token. It submits an {{ARAP}} access request on the client's behalf, binds it to a `deferral_code`, and returns the DTR deferred response (`authorization_pending` with `deferral_code`, `expires_in`, and `interval`). +- **Deny, requestable, but `completion_mode` did not include `deferred`.** The client required synchronous handling, so the AS MUST NOT defer. It returns the same synchronous denial error it would for a non-requestable denial (above); the requestable nature is not exposed. A client that wishes to obtain access through approval MUST offer `completion_mode=deferred`. + +## Idempotent submission + +A token request is not inherently idempotent, so a client that retries an initial request (for example after a lost response or a restart) could otherwise cause the AS to open a second access request and issue a second `deferral_code` for one intent. To prevent duplicate approvals, the AS SHOULD treat a repeated requestable-denial request from the same client for the same requested authorization (the same `authorization_details` and `scope`) as the same request and return the existing `deferral_code` rather than open a new access request. This deduplication is best-effort; a client that requires exactness SHOULD send a stable request identifier where its grant or a deployment profile provides one. + +## The deferral code + +The `deferral_code` is the continuation handle and binds the pending access request in AS state. It is not authority: possessing a `deferral_code` does not authorize anything, and the AS issues a token only after the issuance-time re-evaluation in {{completion}}. Per {{I-D.gerber-oauth-deferred-token-response}} the `deferral_code` is sender-constrained (see {{sender-constraining}}) and is redeemed at most once: once a token is issued for it, a subsequent redemption MUST fail with `invalid_grant`. + +## Polling and completion {#completion} + +The client polls the token endpoint with the deferred grant type (`urn:ietf:params:oauth:grant-type:deferred`) and the `deferral_code`, no faster than `interval`, and backs off on a `slow_down` response per {{I-D.gerber-oauth-deferred-token-response}}. Where the AS supports the DTR client callback notification, the client MAY rely on it in addition to polling; polling remains the authoritative completion path. On resolution the AS re-evaluates the original request with the approval as an input (the {{ARAP}} `approval` object, carried at `context.approval` in the AS's internal evaluation) and: + +- **Approved.** The AS issues the originating grant's token response. The granted authorization (`authorization_details`, and `scope` if used) carries the approved access, which MUST NOT broaden the originating request, and the access token lifetime MUST NOT exceed the recorded approval expiry. + + The approval bounds the credential, not a single token. In this completion mode the AS SHOULD NOT issue a refresh token; if it does, the refresh token and every access token derived from it MUST be bounded by the recorded approval expiry, and a refresh after that expiry MUST fail with `invalid_grant` and requires a new access request. Likewise, a token issued by this profile MUST NOT be exchanged ({{RFC8693}}) into a credential that is longer-lived or broader than the approval. +- **Terminal denial, cancellation, or expiry.** The AS returns `access_denied` (or `expired_token` once the `deferral_code` lifetime has elapsed), per {{I-D.gerber-oauth-deferred-token-response}}. + +The issued access token is the decision representation. This is the token issuance completion mode that {{ARAP}} defers to a downstream profile. Because completion is the issued token, this mode is not surfaced to the client as an {{ARAP}} `result.mode` value; the OAuth token response is the result. + +## Submission input + +The Deferred Token Response carries no in-band interaction channel. Inputs the access request needs are supplied either in the initial token request (for example as `authorization_details` fields) or out of band at the Access Request Service (for example through the {{ARAP}} `form_url` or `request_schema_url`). This profile defines no new in-band input mechanism. + +# AuthZEN Integration {#authzen-integration} + +The AS evaluates the token request by calling its PDP through the AuthZEN Access Evaluation API {{AuthZEN}}, and uses the {{ARAP}} requestable-denial and re-evaluation extensions to drive deferral and completion. These AuthZEN messages are exchanged between the AS and its PDP and never appear on the OAuth wire; the client sees only the OAuth token request and the deferred or final token response. For how the {{ARAP}} lifecycle constructs map to the OAuth wire, see {{mapping}}. + +## Constructing the Access Evaluation + +The AS builds the Access Evaluation request from the token request: + +| OAuth token request | AuthZEN Access Evaluation | +|---|---| +| The principal the token will act as (`client_credentials`: the client; Token Exchange: the `subject_token` subject; authorization code: the end user) | `subject` (with the agent in `subject.properties.act`) | +| Each `authorization_details` object, and any `scope` values | one or more `resource` and `action` pairs | +| Client identity, sender-constraining key, `resource` indicator {{RFC8707}}, and request metadata | `context` members | + +The structural mapping above is fixed. The semantic interpretation of a given `authorization_details` `type` (and of named scopes) into AuthZEN `resource` and `action` values is deployment- or vocabulary-defined, and interoperating parties MUST agree on it. The agent acting for the principal is conveyed in `subject.properties.act` and `context.client_id`. + +A fine-grained read request (read access to one customer record) maps to an evaluation such as: + +~~~ json +{ + "subject": { + "type": "user", + "id": "alice@example.com", + "properties": { "act": { "sub": "agent-7" } } + }, + "resource": { "type": "customer_records", "id": "c-4815" }, + "action": { "name": "read" }, + "context": { "client_id": "agent-7", "dpop_jkt": "0ZcOCORZ..." } +} +~~~ + +A token request may carry several `authorization_details` (and scopes), which the AS evaluates together; it MAY use the AuthZEN Evaluations (bulk) API for this. This profile treats the request as a unit: if any requested authorization is requestable-denied and the client offered `completion_mode=deferred`, the AS defers the whole request and issues the full requested set on approval. Partial issuance (issuing the allowed subset immediately and deferring the rest) is not defined by this profile. + +## Requestable denial + +When the PDP denies but the denial is requestable, it returns `decision: false` with an {{ARAP}} `context.access_request` and denial-binding material (`context.evaluation_id` or a `binding_token`): + +~~~ json +{ + "decision": false, + "context": { + "evaluation_id": "ev_9f2c8a1b", + "reason": "approval_required", + "access_request": { "expires_at": "2026-06-13T12:00:00Z" } + } +} +~~~ + +The AS submits the access request using this binding material, binds the resulting {{ARAP}} task to a `deferral_code`, and returns the DTR deferred response. A `decision: false` with no `context.access_request` is a non-requestable denial and maps to the synchronous OAuth error in the Initial token request rules. + +## Re-evaluation and issuance + +On approval, the AS re-evaluates the same request with the {{ARAP}} `approval` object at `context.approval`: + +~~~ json +{ + "subject": { + "type": "user", + "id": "alice@example.com", + "properties": { "act": { "sub": "agent-7" } } + }, + "resource": { "type": "customer_records", "id": "c-4815" }, + "action": { "name": "read" }, + "context": { + "approval": { "id": "ar_123", "approved_until": "2026-06-13T12:00:00Z" } + } +} +~~~ + +A `decision: true` response authorizes issuance: the AS issues the access token whose granted `authorization_details` reflect the approved `resource` and `action`, with a lifetime bounded by `approved_until`. A `decision: false` at re-evaluation maps to `access_denied` on the next poll; obtaining access then requires a new token request. + +# Examples + +These examples are non-normative. The deferral, polling, and completion are identical regardless of which party issues the token; what varies is the originating grant carrying `completion_mode=deferred` and which token-issuing party gates the approval. The examples show approval gated at the AS (Token Exchange, Authorization Code, Refresh Token), at either authorization server in an ID-JAG chain (the IdP that issues the ID-JAG or the RAS that redeems it), and at a resource server that issues its own tokens. All are valid. The first example shows the full flow; the others show the distinctive initial request and name the deferring party. + +## Token Exchange (approval at the AS) + +An agent acting on behalf of a user requests fine-grained access to a single protected resource. The `authorization_details` (shown decoded for readability) scopes the request to one record: + +~~~ json +[ { "type": "customer_records", "actions": ["read"], "locations": ["https://crm.example/customers/c-4815"] } ] +~~~ + +~~~ http +POST /token HTTP/1.1 +Host: as.example.com +Content-Type: application/x-www-form-urlencoded +DPoP: eyJ... + +grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange +&completion_mode=deferred +&authorization_details=%5B%7B%22type%22%3A%22customer_records%22%2C%22actions%22%3A%5B%22read%22%5D%2C%22locations%22%3A%5B%22https%3A%2F%2Fcrm.example%2Fcustomers%2Fc-4815%22%5D%7D%5D +&subject_token=...&subject_token_type=... +~~~ + +The AS evaluates the request, gets a denied-but-requestable decision, submits an access request, and returns a deferred response: + +~~~ json +{ + "error": "authorization_pending", + "deferral_code": "8d67dc78-7faa-4d41-aabd-67707b374255", + "expires_in": 10800, + "interval": 60 +} +~~~ + +The client polls until the record's data owner resolves the request out of band: + +~~~ http +POST /token HTTP/1.1 +Host: as.example.com +Content-Type: application/x-www-form-urlencoded +DPoP: eyJ... + +grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adeferred +&deferral_code=8d67dc78-7faa-4d41-aabd-67707b374255 +~~~ + +On approval the AS re-evaluates and issues an access token carrying the approved authorization, with a lifetime bounded by the approval expiry: + +~~~ json +{ + "access_token": "eyJ...", + "token_type": "DPoP", + "expires_in": 900, + "authorization_details": [ + { "type": "customer_records", "actions": ["read"], "locations": ["https://crm.example/customers/c-4815"] } + ] +} +~~~ + +If the request is denied or cancelled, the poll returns `access_denied` and no token is issued. + +## Authorization Code (approval at the AS) + +A user authorizes an agent through the authorization code flow, requesting fine-grained authority with `authorization_details` at the authorization endpoint. When the agent exchanges the code, the AS finds the requested action needs manager approval and defers. + +~~~ http +POST /token HTTP/1.1 +Host: as.example.com +Content-Type: application/x-www-form-urlencoded +DPoP: eyJ... + +grant_type=authorization_code +&code=SplxlOBeZQQYbYS6WxSbIA +&redirect_uri=https%3A%2F%2Fagent.example%2Fcallback +&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk +&completion_mode=deferred +~~~ + +The requested `authorization_details` were carried on the authorization request and are bound to the code, so they are not repeated here. The AS returns a deferred response and the agent polls and completes as above. + +## Refresh Token (approval at the AS) + +A long-running agent holds a refresh token scoped to one project. When it refreshes to reach a resource outside that scope, the AS defers for the new resource owner's approval rather than issuing immediately. + +~~~ http +POST /token HTTP/1.1 +Host: as.example.com +Content-Type: application/x-www-form-urlencoded +DPoP: eyJ... + +grant_type=refresh_token +&refresh_token=8xLOxBtZp8 +&completion_mode=deferred +&authorization_details=%5B%7B%22type%22%3A%22documents%22%2C%22actions%22%3A%5B%22read%22%5D%2C%22locations%22%3A%5B%22https%3A%2F%2Ffiles.example%2Fprojects%2Fatlas%2Fspec.pdf%22%5D%7D%5D +~~~ + +The AS returns a deferred response and the agent polls and completes as above. Any token the AS ultimately issues remains scoped to the approved resource and bounded by the new approval's expiry, and the refresh token cannot be used to broaden or extend it (see {{completion}}). + +## Identity Assertion Authorization Grant, ID-JAG (approval at the IdP or the RAS) + +An ID-JAG flow has two token issuers, and approval may be gated at either. + +The agent first obtains an ID-JAG from the enterprise identity provider (IdP) by Token Exchange. The **IdP** may defer here, gating issuance of the ID-JAG itself: + +~~~ http +POST /token HTTP/1.1 +Host: idp.acme.example +Content-Type: application/x-www-form-urlencoded +DPoP: eyJ... + +grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange +&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aid-jag +&audience=https%3A%2F%2Fcrm.acme.example +&subject_token=...&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aid_token +&completion_mode=deferred +&authorization_details=%5B%7B%22type%22%3A%22crm%22%2C%22actions%22%3A%5B%22export%22%5D%2C%22locations%22%3A%5B%22https%3A%2F%2Fcrm.acme.example%2Fcontacts%22%5D%7D%5D +~~~ + +Alternatively the IdP issues the ID-JAG synchronously and the **Resource Authorization Server (RAS)** defers when the agent redeems it (`grant_type=jwt-bearer`, `assertion`) for an access token: + +~~~ http +POST /oauth2/token HTTP/1.1 +Host: crm.acme.example +Content-Type: application/x-www-form-urlencoded +DPoP: eyJ... + +grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer +&assertion=eyJhbGciOiJFUzI1Ni... (the ID-JAG) +&completion_mode=deferred +&authorization_details=%5B%7B%22type%22%3A%22crm%22%2C%22actions%22%3A%5B%22export%22%5D%2C%22locations%22%3A%5B%22https%3A%2F%2Fcrm.acme.example%2Fcontacts%22%5D%7D%5D +~~~ + +In both placements the deferring party returns the same `authorization_pending` deferred response, and the agent polls and completes the same way. + +## Resource Server (approval at the RS) + +Some resources issue their own access tokens, acting as their own authorization server. The agent requests a token directly from the resource, which gates approval itself before issuing: + +~~~ http +POST /token HTTP/1.1 +Host: files.acme.example +Content-Type: application/x-www-form-urlencoded +DPoP: eyJ... + +grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange +&completion_mode=deferred +&authorization_details=%5B%7B%22type%22%3A%22files%22%2C%22actions%22%3A%5B%22delete%22%5D%2C%22locations%22%3A%5B%22https%3A%2F%2Ffiles.acme.example%2Fprojects%2Fq4%22%5D%7D%5D +&subject_token=...&subject_token_type=... +~~~ + +The resource returns a deferred response and the agent polls and completes as above; here the resource server is the party that gates and records the approval. + +# Mapping to the Access Request and Approval Profile {#mapping} + +The OAuth wire surface replaces several {{ARAP}} constructs; the rest map directly. For how the OAuth token request maps to AuthZEN Access Evaluation messages, see {{authzen-integration}}. + +| ARAP construct | OAuth binding in this profile | +|---|---| +| Requestable denial (`context.access_request`) | The AS's internal trigger to defer. Not surfaced to the client. | +| Access request submission | Performed by the AS on the client's behalf during evaluation of the token request. | +| Idempotency key | Best-effort dedup by client and requested authorization (see {{completion}} and Idempotent submission). | +| Task handle | The DTR `deferral_code`. | +| Task Status Endpoint and polling | Polling the token endpoint with the deferred grant type. | +| Callback completion | The optional DTR client callback notification. | +| `approval` object (`context.approval`) | Input to the AS's issuance-time re-evaluation. | +| `reevaluate` completion mode | Replaced by token issuance (this profile's completion mode). | +| `binding_token` / `evaluation_id` | Bound to the `deferral_code` in AS state; not exposed to the client. | +| `approved_until` | Upper bound on the issued access token lifetime. | +| Denial reason and `next_action` | OAuth error codes (`access_denied`, `expired_token`). | + +# Sender-Constraining and Cancellation {#sender-constraining} + +Per {{I-D.gerber-oauth-deferred-token-response}}, the `deferral_code` and the issued access token are sender-constrained using DPoP {{RFC9449}} or mutual TLS {{RFC8705}}; a public client MUST present DPoP on the initial token request, and every polling and revocation request MUST carry proof of possession for the same key. + +To cancel a pending request, the client revokes the `deferral_code` at the revocation endpoint {{RFC7009}} (with `token_type_hint` set to `urn:ietf:params:oauth:token-type:deferral-code`, the type defined by {{I-D.gerber-oauth-deferred-token-response}}). The AS MUST cancel the underlying access request. + +# Privacy Considerations + +- The requested and approved `authorization_details` can carry sensitive data (account numbers, amounts, resource identifiers). Parties MUST apply data minimization and protect them in transit and at rest. +- The AS MUST NOT expose the approver's identity, internal approval state, or {{ARAP}} binding material to the client; the client sees only the `deferral_code` and the eventual token or error. +- `error_description` and any human-readable text returned with a deferred or error response MUST be minimized and MUST NOT leak approver identity or internal policy detail, consistent with {{I-D.gerber-oauth-deferred-token-response}}. + +# Security Considerations + +- **A deferred response is not a grant.** `authorization_pending` with a `deferral_code` is not access. Denial remains denial: the AS issues a token only after the issuance-time re-evaluation, and a non-requestable or terminally denied request yields an OAuth error, never a token. +- **Issuance-time decision, bounded by lifetime.** Because the authoritative decision is made at issuance and the resource server may validate the token offline, deployments MUST bound the access token lifetime by the approval expiry and SHOULD keep lifetimes short and use token introspection where current revocation matters. Refresh tokens and token exchange MUST NOT be used to extend an approval beyond its recorded expiry (see {{completion}}); otherwise the approval bound is defeated. +- **Least privilege and audience.** The issued authorization MUST reflect the approved access and MUST NOT broaden the originating request, and the token SHOULD be audience-restricted to the intended resource using resource indicators {{RFC8707}} so an approved token cannot be replayed at an unintended resource server. +- **Request integrity.** Because the requested `authorization_details` drive a fine-grained authorization decision, deployments SHOULD protect them in transit and from tampering, for example with Pushed Authorization Requests {{?RFC9126}} or a signed request, so the request the AS evaluates is the one the client intended. +- **The deferral code is not authority.** It is opaque, sender-constrained, and bound to the client it was issued to; it is usable only at the token and revocation endpoints. +- **The decision stays behind the AS.** The token request carries a RAR request and the issued token carries the approved grant; neither carries an AuthZEN request or decision object, which keeps the decision engine off the client surface and avoids substitution or replay of decision payloads. + +# Relationship to Other Specifications {#relationship} + +- {{ARAP}}: this profile is its OAuth token issuance completion mode. {{ARAP}} owns the requestable denial, access request, approval, and audit lifecycle; this profile binds completion to OAuth token issuance. +- {{I-D.gerber-oauth-deferred-token-response}}: the asynchronous transport. This profile adds no new grant type; it uses DTR's `completion_mode`, deferred grant, and polling. +- {{RFC9396}}: carries the requested authorization in the token request and the approved authorization in the issued token. +- {{RFC8693}}: Token Exchange is a natural originating grant for on-behalf-of agent flows, and DTR can defer it; this profile composes with it unchanged. +- {{?I-D.brossard-oauth-rar-authzen}}: that draft places an AuthZEN request and response inside `authorization_details`. This profile deliberately does not. The decision stays behind the AS and the token carries the approved grant, not the evaluation. +- {{?RFC8628}}: the Device Authorization Grant solves a related asynchronous case for the same end user; DTR (and therefore this profile) does not assume the approver is the requesting user, which is the agentic case this profile targets. + +# IANA Considerations + +This profile defines no new OAuth grant types, parameters, or error codes; it reuses {{I-D.gerber-oauth-deferred-token-response}} and {{RFC9396}}. It requests no IANA registrations. Any `authorization_details` type used by a deployment is registered per {{RFC9396}}. From 801ece8c9e2fcb338cc055c869e1fe0fb12cea6e Mon Sep 17 00:00:00 2001 From: Karl McGuinness Date: Thu, 25 Jun 2026 11:13:17 -0700 Subject: [PATCH 2/6] Generalize AROP to three transport bindings (DTR, CIBA, txn-challenge) Restructures the profile so the asynchronous wait is pluggable across three OAuth mechanisms rather than hardwired to the Deferred Token Response. - Transport-neutral Common Processing core: resolution outcomes, continuation handle, polling/completion, idempotent submission, and access request input, defined once and shared by all bindings. - Three peer binding sections (Deferred Token Response, CIBA, OAuth Transaction Authorization Challenge) plus a per-transport comparison table. CIBA covers poll/ping/push delivery and self-approval; txn-challenge covers the resource-signed challenge flow. - PEP-placement axis as a deployment dimension: AS as PEP (DTR, CIBA), resource as PEP (txn-challenge), or both. Examples cover all three. - DTR example expanded into a full worked trace: AuthZEN Access Evaluation request/response and ARAP submission, task, and re-evaluation, each internal message marked off the OAuth wire. - evaluation_id vs binding_token denial-binding shown for co-located vs separate Access Request Service. - Adds CIBA (OIDC-CIBA Core 1.0) and txn-challenge references. Completion mode remains token issuance (not reevaluate); no new OAuth grant type, endpoint, or parameter of its own. --- ...uthzen-access-request-oauth-profile-1_0.md | 535 +++++++++++++----- 1 file changed, 383 insertions(+), 152 deletions(-) diff --git a/profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md b/profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md index d72d366..714b64b 100644 --- a/profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md +++ b/profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md @@ -13,6 +13,9 @@ keyword: - OAuth - access token - deferred token response + - CIBA + - backchannel authentication + - transaction authorization challenge - rich authorization requests - AI agent - access request @@ -41,6 +44,43 @@ normative: RFC7009: RFC8707: I-D.gerber-oauth-deferred-token-response: + I-D.rosomakho-oauth-txn-challenge: + title: "OAuth Transaction Authorization Challenge" + target: "https://datatracker.ietf.org/doc/draft-rosomakho-oauth-txn-challenge/" + author: + - + ins: Y. Rosomakho + name: Yaroslav Rosomakho + - + ins: B. Campbell + name: Brian Campbell + - + ins: K. McGuinness + name: Karl McGuinness + - + ins: P. Kasselman + name: Pieter Kasselman + date: 2026-06-25 + OIDC-CIBA: + title: "OpenID Connect Client-Initiated Backchannel Authentication Flow - Core 1.0" + target: "https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html" + author: + - + ins: G. Fernandez + name: Gonzalo Fernandez Rodriguez + - + ins: F. Walter + name: Florian Walter + - + ins: A. Nennker + name: Axel Nennker + - + ins: D. Tonge + name: Dave Tonge + - + ins: B. Campbell + name: Brian Campbell + date: 2021-09-01 AuthZEN: title: "Authorization API 1.0" target: "https://openid.github.io/authzen/" @@ -66,7 +106,7 @@ normative: --- abstract -This specification defines an OAuth binding for the AuthZEN Access Request and Approval Profile in which an approved access request completes as an issued OAuth access token. When an Authorization Server evaluates a token request as an AuthZEN Access Evaluation and the decision is denied but requestable, the Authorization Server resolves it through an access request and completes by issuing an access token that carries the approved authorization. The asynchronous wait is carried by the OAuth Deferred Token Response: the Authorization Server returns a deferral code, and the client polls the token endpoint until a token is issued or the request is denied. The binding adds no new OAuth grant type or protocol parameter; it composes the Deferred Token Response, Rich Authorization Requests, and the Access Request and Approval Profile. +This specification defines an OAuth binding for the AuthZEN Access Request and Approval Profile in which an approved access request completes as an issued OAuth access token. When a request is evaluated as an AuthZEN Access Evaluation and the decision is denied but requestable, the denial is resolved through an access request and completes by issuing an access token that carries the approved authorization. The asynchronous wait is carried by an existing OAuth asynchronous authorization mechanism; this profile defines three transport bindings: the OAuth Deferred Token Response, Client-Initiated Backchannel Authentication (CIBA), and the OAuth Transaction Authorization Challenge. The binding adds no new OAuth grant type, endpoint, or protocol parameter of its own; it composes these mechanisms with Rich Authorization Requests and the Access Request and Approval Profile. --- middle @@ -74,11 +114,17 @@ This specification defines an OAuth binding for the AuthZEN Access Request and A The AuthZEN Access Request and Approval Profile {{ARAP}} defines how a denied but requestable authorization decision is resolved out of band and re-evaluated. It defines a single completion mode, `reevaluate`, in which the Policy Decision Point (PDP) remains authoritative at enforcement time, and it explicitly leaves completion modes that bind approval to a specific issuance flow, such as OAuth token issuance where the issued token is itself the decision representation, to a downstream profile. -This specification is that downstream profile for OAuth 2.1 {{I-D.ietf-oauth-v2-1}}. It defines a **token issuance completion mode**: a client requests a token, the Authorization Server (AS) evaluates the request as an AuthZEN Access Evaluation {{AuthZEN}}, and when the decision is denied but requestable the AS resolves it through an {{ARAP}} access request and completes by issuing an access token that carries the approved authorization. +This specification is that downstream profile for OAuth 2.1 {{I-D.ietf-oauth-v2-1}}. It defines a **token issuance completion mode**: a request is evaluated as an AuthZEN Access Evaluation {{AuthZEN}}, and when the decision is denied but requestable it is resolved through an {{ARAP}} access request and completes by issuing an access token that carries the approved authorization. -The asynchronous wait is carried by the OAuth Deferred Token Response {{I-D.gerber-oauth-deferred-token-response}} (DTR): rather than returning a token or an error, the AS returns a `deferral_code`, and the client polls the token endpoint until the token is issued or the request is denied. +The completion semantics are the same regardless of how the asynchronous wait is carried: the issued access token is the decision representation, the authoritative decision is made at issuance time, and the issued credential is bounded by the recorded approval expiry. What varies is the OAuth mechanism that carries the request and the wait. This profile defines three transport bindings: -The binding adds no new OAuth grant type and no new protocol parameters. It rides on DTR (`completion_mode=deferred` and the deferred grant type) over any originating grant, including Token Exchange {{RFC8693}}, and uses Rich Authorization Requests {{RFC9396}} (RAR) to carry both the requested and the approved authorization. +- **Deferred Token Response** {{I-D.gerber-oauth-deferred-token-response}} (DTR): the client makes a token request, and rather than returning a token or an error the Authorization Server (AS) returns a `deferral_code`; the client polls the token endpoint until the token is issued or the request is denied. +- **Client-Initiated Backchannel Authentication** {{OIDC-CIBA}} (CIBA): the client makes a backchannel authentication request, the AS returns an `auth_req_id`, and approval is obtained out of band from a decoupled party; the client polls the token endpoint (or is notified) until completion. +- **Transaction Authorization Challenge** {{I-D.rosomakho-oauth-txn-challenge}} (transaction challenge): a protected resource returns a signed challenge for an operation it cannot authorize from the presented token; the client presents the challenge to the AS, the AS obtains approval and issues a transaction-scoped token, and the client re-presents that token to the resource. + +These bindings also differ in where the requestable denial originates. In {{ARAP}} the Policy Enforcement Point (PEP) and PDP roles can be deployed in more than one place, and all of the following are valid: the AS evaluates and defers its own token request (AS as PEP); a protected resource asserts the requestable denial as a signed challenge that the AS consumes (resource as PEP); or both apply policy, the resource asserting the challenge and the AS additionally evaluating before it issues. This profile accommodates all three placements. + +The binding adds no new OAuth grant type, endpoint, or protocol parameter of its own. The deferred grant belongs to DTR, the backchannel endpoint and CIBA grant belong to CIBA, and the transaction authorization endpoint belongs to the transaction challenge draft; this profile only binds them to the {{ARAP}} lifecycle and uses Rich Authorization Requests {{RFC9396}} (RAR) to carry both the requested and the approved authorization. # Requirements Notation and Conventions @@ -87,8 +133,9 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S # Design Goals - **OAuth-native completion.** In this mode the issued access token is the decision representation. Authorization is exercised by presenting the token to a resource server, not by carrying an AuthZEN decision object on the wire. -- **Compose, do not reinvent.** The asynchronous transport is DTR, the fine-grained request and grant are RAR, and the approval lifecycle is {{ARAP}}. This profile only binds them; it defines no new endpoints, grant types, or parameters. -- **The decision stays behind the AS.** The token request carries a RAR request, the AS maps it to an AuthZEN evaluation internally, and the issued token carries the approved grant. The token never carries an AuthZEN request or decision object (see {{relationship}}). +- **Compose, do not reinvent.** The asynchronous transport is an existing OAuth mechanism (DTR, CIBA, or the transaction challenge), the fine-grained request and grant are RAR, and the approval lifecycle is {{ARAP}}. This profile only binds them; it defines no new endpoints, grant types, or parameters of its own. +- **One completion model, several transports.** The resolution outcomes, the issuance-time decision, the least-privilege constraint, and the approval-bounded lifetime are defined once and shared by all three bindings (see {{completion}}). A binding section specializes only the transport surface: how the request enters, the name of the continuation handle, where the client polls or is notified, and how completion is delivered (see {{bindings}}). +- **The decision is not carried on the wire.** The request carries a RAR request (or, in the transaction challenge binding, a resource-signed challenge), the evaluating party maps it to an AuthZEN evaluation internally, and the issued token carries the approved grant. No AuthZEN request or decision object travels between the client and the AS (see {{relationship}}). - **Least privilege.** The issued token's granted authorization (`authorization_details`, and `scope` if used) reflects the approved access and MUST NOT broaden the originating request. The token SHOULD be audience-restricted to the intended resource using resource indicators {{RFC8707}}, so a narrowly approved token is not presentable at an unintended resource server. - **Authority model: an issuance-time decision, bounded by token lifetime.** {{ARAP}}'s base model keeps the PDP authoritative at enforcement through per-call re-evaluation. A token issuance completion mode necessarily moves the authoritative decision to issuance time: the AS evaluates, issues a bearer token, and a resource server later validates that token, possibly offline. This profile therefore trades per-call re-evaluation for an issuance-time decision bounded by the token's lifetime. Deployments preserve currency with short token lifetimes and token introspection, the issued access token's lifetime MUST NOT exceed the approval expiry the Access Request Service recorded, and the approval bounds the whole credential rather than a single token (see {{completion}}). @@ -97,42 +144,49 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S This profile uses the roles of {{AuthZEN}} and {{ARAP}}, mapped onto OAuth: Authorization Server (AS): -: The OAuth Authorization Server. In this profile the AS acts as (or fronts) the AuthZEN PDP for the token request and is the token issuer. Any OAuth token-issuing endpoint can play this role, so approval is gated wherever the token is requested: a single AS, the identity provider (IdP) or Resource Authorization Server (RAS) in an ID-JAG {{?I-D.ietf-oauth-identity-assertion-authz-grant}} chain, or a resource server that issues its own tokens. +: The OAuth Authorization Server and the token issuer. In the DTR and CIBA bindings the AS also acts as (or fronts) the AuthZEN PDP for the request. In the transaction challenge binding the AS consumes a requestable denial asserted by a protected resource, and MAY additionally apply its own policy before issuing. Any OAuth token-issuing endpoint can play this role, so approval is gated wherever the token is requested: a single AS, the identity provider (IdP) or Resource Authorization Server (RAS) in an ID-JAG {{?I-D.ietf-oauth-identity-assertion-authz-grant}} chain, or a resource server that issues its own tokens. Access Request Service (ARS): -: The {{ARAP}} role that runs the approval workflow. The AS submits the access request to it and tracks resolution. It is internal to the AS deployment and not exposed to the client. +: The {{ARAP}} role that runs the approval workflow. The evaluating party submits the access request to it and tracks resolution. It is internal to the deployment and not exposed to the client. + +Policy Enforcement Point (PEP): +: The {{ARAP}} role that detects the requestable denial. In this profile the PEP may be the AS (DTR, CIBA), a protected resource that asserts a signed challenge (transaction challenge), or both. Client: -: The OAuth client (an agent, or a Policy Enforcement Point acting for one) that makes the token request and polls for completion. +: The OAuth client (an agent, or a Policy Enforcement Point acting for one) that makes the request and polls for, or is notified of, completion. Resource Server (RS): -: The server that accepts the issued access token and enforces the authorization it carries. In this profile the RS is the enforcement point: it validates the token, possibly offline, rather than re-evaluating against a PDP per call. +: The server that accepts the issued access token and enforces the authorization it carries. In this profile the RS is the enforcement point at the time of use: it validates the token, possibly offline, rather than re-evaluating against a PDP per call. In the transaction challenge binding the RS additionally originates the requestable denial as a signed challenge. -This profile also uses `completion_mode`, `deferral_code`, and the deferred grant type from {{I-D.gerber-oauth-deferred-token-response}}, and `authorization_details` from {{RFC9396}}. +Continuation handle: +: The opaque value that binds the pending access request in the issuer's state and that the client redeems to obtain the token. Each binding names it differently: the DTR `deferral_code`, the CIBA `auth_req_id`, and the transaction challenge `transaction_authorization_id`. + +This profile also uses `authorization_details` from {{RFC9396}}, and the transport-specific parameters of {{I-D.gerber-oauth-deferred-token-response}}, {{OIDC-CIBA}}, and {{I-D.rosomakho-oauth-txn-challenge}} as defined in the binding sections. # Protocol Overview +Across all three bindings the shape is the same: an asynchronous authorization request, a pending response carrying a continuation handle, a wait resolved out of band, and completion as an issued access token. The diagram shows the common flow; the binding sections give the per-transport entry point, handle, and completion surface. + ~~~ ascii-art +--------+ +---------------------+ +------------------+ | Client | | Authorization Server| | Access Request | -| | | (PDP + issuer) | | Service (ARS) | +| | | (issuer + PDP) | | Service (ARS) | +---+----+ +----------+----------+ +--------+---------+ | | | - | 1. token request | | - | completion_mode=deferred | | - | authorization_details (RAR) | | + | 1. asynchronous authorization request | | + | (RAR authorization_details) | | |---------------------------------------->| | | | evaluate (AuthZEN); | | | denied but requestable | | |--------------------------->| | | submit access request | - | 2. authorization_pending | | - | deferral_code, expires_in, interval | | + | 2. pending: continuation handle, | | + | expires_in, interval | | |<----------------------------------------| | | | ... out-of-band approval | - | 3. poll (grant_type=deferred) | | + | 3. poll (or notification) | | |---------------------------------------->| | - | authorization_pending | | + | pending | | |<----------------------------------------| | | | approved | | |<---------------------------| @@ -142,76 +196,83 @@ This profile also uses `completion_mode`, `deferral_code`, and the deferred gran |<----------------------------------------| | ~~~ +In the transaction challenge binding the requestable denial originates at a protected resource, which returns a signed challenge to the client before step 1; the client then presents that challenge to the AS, and after completion re-presents the issued token to the resource (see {{binding-txn}}). + # Discovery -An AS that supports the Deferred Token Response advertises `deferred_token_response_supported` in its authorization server metadata ({{I-D.gerber-oauth-deferred-token-response}}). In a multi-issuer topology, each token-issuing party (a single AS, the IdP and the RAS in an ID-JAG chain, or a resource server that issues its own tokens) advertises this independently in its own metadata. A client participates by including `completion_mode=deferred` on its token request and being prepared for any of three outcomes: a synchronous token response, a deferred response (`authorization_pending` with a `deferral_code`), or an error. This profile defines no additional discovery metadata. Whether a given token request is evaluated as an AuthZEN Access Evaluation and may be deferred for approval is an AS policy decision and is not separately advertised. +The mechanism a client uses is the one the issuer advertises, and each transport advertises independently in authorization server metadata. This profile defines no additional discovery metadata of its own; whether a given request is evaluated as an AuthZEN Access Evaluation and may be deferred for approval is a policy decision and is not separately advertised. -# Requesting and Completing a Deferred Token +- **DTR.** The AS advertises `deferred_token_response_supported` ({{I-D.gerber-oauth-deferred-token-response}}). A client participates by including `completion_mode=deferred` on its token request and being prepared for a synchronous token, a deferred response, or an error. +- **CIBA.** The AS advertises a `backchannel_authentication_endpoint` and `backchannel_token_delivery_modes_supported` ({{OIDC-CIBA}}); a client uses the delivery mode (`poll`, `ping`, or `push`) the AS supports. +- **Transaction challenge.** The AS advertises a `transaction_authorization_endpoint`, and the protected resource advertises the keys it signs challenges with (`txn_challenge_jwks_uri`) and its signing algorithms, per {{I-D.rosomakho-oauth-txn-challenge}}. Unlike the other two bindings, this introduces a trust relationship in which the AS validates challenges signed by the resource (see {{security}}). -## Initial token request +In a multi-issuer topology, each token-issuing party (a single AS, the IdP and the RAS in an ID-JAG chain, or a resource server that issues its own tokens) advertises its supported mechanisms independently in its own metadata. -The client makes an ordinary token request and signals deferred completion with `completion_mode=deferred` ({{I-D.gerber-oauth-deferred-token-response}}). The requested authorization is expressed with `authorization_details` ({{RFC9396}}), with `scope`, or with both. The AS maps the request to an AuthZEN Access Evaluation and evaluates it against its PDP, as described in {{authzen-integration}}. The AS resolves as follows: +# Common Processing {#completion} -- **Allow.** The AS returns the originating grant's normal token response synchronously. -- **Deny, not requestable.** The AS returns a synchronous token-endpoint error: `invalid_authorization_details` ({{RFC9396}}) when the denial concerns the requested `authorization_details`, `invalid_scope` when it concerns requested `scope`, and otherwise `invalid_grant`. `access_denied`, `authorization_pending`, and the other Deferred Token Response error codes are reserved for the deferred path and MUST NOT be returned here. -- **Deny, requestable, client offered `completion_mode=deferred`.** The AS MUST NOT issue a token. It submits an {{ARAP}} access request on the client's behalf, binds it to a `deferral_code`, and returns the DTR deferred response (`authorization_pending` with `deferral_code`, `expires_in`, and `interval`). -- **Deny, requestable, but `completion_mode` did not include `deferred`.** The client required synchronous handling, so the AS MUST NOT defer. It returns the same synchronous denial error it would for a non-requestable denial (above); the requestable nature is not exposed. A client that wishes to obtain access through approval MUST offer `completion_mode=deferred`. +This section defines the resolution outcomes and completion semantics shared by all three bindings. The binding sections ({{bindings}}) specialize only the transport surface. For how the request maps to an AuthZEN Access Evaluation, see {{authzen-integration}}. + +## Resolution outcomes + +The evaluating party resolves an asynchronous authorization request as follows: + +- **Allow.** The transport's normal success response is returned synchronously (a token, for the request mechanisms that issue one directly). +- **Deny, not requestable.** A synchronous error is returned in the transport's error form: at the token endpoint, `invalid_authorization_details` ({{RFC9396}}) when the denial concerns the requested `authorization_details`, `invalid_scope` when it concerns requested `scope`, and otherwise `invalid_grant`. The pending and terminal-denial codes (`authorization_pending`, `access_denied`, `expired_token`, `slow_down`) are reserved for the deferred path and MUST NOT be returned here. +- **Deny, requestable.** The evaluating party MUST NOT issue a token. It submits an {{ARAP}} access request on the client's behalf, binds it to a continuation handle, and returns the transport's pending response (carrying the handle, `expires_in`, and `interval`). +- **Deny, requestable, but the client did not opt into asynchronous completion.** Where a binding requires the client to signal that it can handle a deferred outcome (DTR's `completion_mode=deferred`), and the client did not, the evaluating party MUST NOT defer. It returns the same synchronous denial error it would for a non-requestable denial; the requestable nature is not exposed. A client that wishes to obtain access through approval MUST opt in. ## Idempotent submission -A token request is not inherently idempotent, so a client that retries an initial request (for example after a lost response or a restart) could otherwise cause the AS to open a second access request and issue a second `deferral_code` for one intent. To prevent duplicate approvals, the AS SHOULD treat a repeated requestable-denial request from the same client for the same requested authorization (the same `authorization_details` and `scope`) as the same request and return the existing `deferral_code` rather than open a new access request. This deduplication is best-effort; a client that requires exactness SHOULD send a stable request identifier where its grant or a deployment profile provides one. +An asynchronous authorization request is not inherently idempotent, so a client that retries an initial request (for example after a lost response or a restart) could otherwise cause the issuer to open a second access request and issue a second continuation handle for one intent. To prevent duplicate approvals, the issuer SHOULD treat a repeated requestable-denial request from the same client for the same requested authorization (the same `authorization_details` and `scope`, or the same challenge `txn` in the transaction binding) as the same request and return the existing handle rather than open a new access request. This deduplication is best-effort; a client that requires exactness SHOULD send a stable request identifier where its transport provides one. -## The deferral code +## Continuation handle -The `deferral_code` is the continuation handle and binds the pending access request in AS state. It is not authority: possessing a `deferral_code` does not authorize anything, and the AS issues a token only after the issuance-time re-evaluation in {{completion}}. Per {{I-D.gerber-oauth-deferred-token-response}} the `deferral_code` is sender-constrained (see {{sender-constraining}}) and is redeemed at most once: once a token is issued for it, a subsequent redemption MUST fail with `invalid_grant`. +The continuation handle is the redemption handle and binds the pending access request in the issuer's state. It is not authority: possessing a handle does not authorize anything, and a token is issued only after the issuance-time re-evaluation below. The handle is sender-constrained (see {{sender-constraining}}) and is redeemed at most once: once a token is issued for it, a subsequent redemption MUST fail with `invalid_grant`. -## Polling and completion {#completion} +## Polling and completion -The client polls the token endpoint with the deferred grant type (`urn:ietf:params:oauth:grant-type:deferred`) and the `deferral_code`, no faster than `interval`, and backs off on a `slow_down` response per {{I-D.gerber-oauth-deferred-token-response}}. Where the AS supports the DTR client callback notification, the client MAY rely on it in addition to polling; polling remains the authoritative completion path. On resolution the AS re-evaluates the original request with the approval as an input (the {{ARAP}} `approval` object, carried at `context.approval` in the AS's internal evaluation) and: +The client redeems the continuation handle at the binding's completion endpoint, no faster than `interval`, and backs off on a `slow_down` response. Where a binding offers a notification channel (the DTR client callback, CIBA `ping`), the client MAY rely on it in addition to polling; polling remains the authoritative completion path. A binding MAY also deliver the token directly rather than by redemption (CIBA `push`), in which case there is no polling to be authoritative over. On resolution the evaluating party re-evaluates the original request with the approval as an input (the {{ARAP}} `approval` object, carried at `context.approval` in the internal evaluation) and: -- **Approved.** The AS issues the originating grant's token response. The granted authorization (`authorization_details`, and `scope` if used) carries the approved access, which MUST NOT broaden the originating request, and the access token lifetime MUST NOT exceed the recorded approval expiry. +- **Approved.** The issuer issues the access token. The granted authorization (`authorization_details`, and `scope` if used) carries the approved access, which MUST NOT broaden the originating request, and the access token lifetime MUST NOT exceed the recorded approval expiry. - The approval bounds the credential, not a single token. In this completion mode the AS SHOULD NOT issue a refresh token; if it does, the refresh token and every access token derived from it MUST be bounded by the recorded approval expiry, and a refresh after that expiry MUST fail with `invalid_grant` and requires a new access request. Likewise, a token issued by this profile MUST NOT be exchanged ({{RFC8693}}) into a credential that is longer-lived or broader than the approval. -- **Terminal denial, cancellation, or expiry.** The AS returns `access_denied` (or `expired_token` once the `deferral_code` lifetime has elapsed), per {{I-D.gerber-oauth-deferred-token-response}}. + The approval bounds the credential, not a single token. In this completion mode the issuer SHOULD NOT issue a refresh token; if it does, the refresh token and every access token derived from it MUST be bounded by the recorded approval expiry, and a refresh after that expiry MUST fail with `invalid_grant` and requires a new access request. Likewise, a token issued by this profile MUST NOT be exchanged ({{RFC8693}}) into a credential that is longer-lived or broader than the approval. +- **Terminal denial, cancellation, or expiry.** The issuer returns `access_denied` (or `expired_token` once the handle lifetime has elapsed). The issued access token is the decision representation. This is the token issuance completion mode that {{ARAP}} defers to a downstream profile. Because completion is the issued token, this mode is not surfaced to the client as an {{ARAP}} `result.mode` value; the OAuth token response is the result. -## Submission input +## Access request input -The Deferred Token Response carries no in-band interaction channel. Inputs the access request needs are supplied either in the initial token request (for example as `authorization_details` fields) or out of band at the Access Request Service (for example through the {{ARAP}} `form_url` or `request_schema_url`). This profile defines no new in-band input mechanism. +These transports carry no in-band interaction channel for the approval itself. Inputs the access request needs are supplied either in the initial request (for example as `authorization_details` fields, or CIBA's `binding_message`) or out of band at the Access Request Service (for example through the {{ARAP}} `form_url` or `request_schema_url`). This profile defines no new in-band input mechanism. # AuthZEN Integration {#authzen-integration} -The AS evaluates the token request by calling its PDP through the AuthZEN Access Evaluation API {{AuthZEN}}, and uses the {{ARAP}} requestable-denial and re-evaluation extensions to drive deferral and completion. These AuthZEN messages are exchanged between the AS and its PDP and never appear on the OAuth wire; the client sees only the OAuth token request and the deferred or final token response. For how the {{ARAP}} lifecycle constructs map to the OAuth wire, see {{mapping}}. +The requestable denial and re-evaluation are driven through the AuthZEN Access Evaluation API {{AuthZEN}} and the {{ARAP}} extensions. These AuthZEN messages are exchanged between the evaluating party and its PDP and never appear on the OAuth wire; the client sees only the OAuth request and the pending or final response. For how the {{ARAP}} lifecycle constructs map to the OAuth wire, see {{mapping}}. + +## Where the requestable denial originates + +{{ARAP}} allows the PEP and PDP roles to be deployed in more than one place, and this profile supports three placements: + +- **AS as PEP (DTR, CIBA).** The AS evaluates the request against its PDP, derives the requestable denial itself, and defers. +- **Resource as PEP (transaction challenge).** A protected resource detects that the presented token cannot authorize the operation, asserts the requestable denial as a signed `transaction_challenge`, and the AS treats the validated challenge as the requestable-denial signal rather than re-deriving the decision from scratch. +- **Both.** The resource asserts the challenge and the AS additionally evaluates against its own PDP before issuing, so the decision is the conjunction of resource policy and AS policy. + +In every placement the access request, approval, and issuance-time re-evaluation are the same; only the origin of the requestable denial differs. ## Constructing the Access Evaluation -The AS builds the Access Evaluation request from the token request: +The evaluating party builds the Access Evaluation request from the OAuth request (or the challenge): -| OAuth token request | AuthZEN Access Evaluation | +| OAuth request | AuthZEN Access Evaluation | |---|---| -| The principal the token will act as (`client_credentials`: the client; Token Exchange: the `subject_token` subject; authorization code: the end user) | `subject` (with the agent in `subject.properties.act`) | +| The principal the token will act as (`client_credentials`: the client; Token Exchange: the `subject_token` subject; authorization code: the end user; CIBA: the party in `login_hint`; transaction challenge: the actor in the challenge `act`, when present) | `subject` (with the agent in `subject.properties.act`) | | Each `authorization_details` object, and any `scope` values | one or more `resource` and `action` pairs | | Client identity, sender-constraining key, `resource` indicator {{RFC8707}}, and request metadata | `context` members | The structural mapping above is fixed. The semantic interpretation of a given `authorization_details` `type` (and of named scopes) into AuthZEN `resource` and `action` values is deployment- or vocabulary-defined, and interoperating parties MUST agree on it. The agent acting for the principal is conveyed in `subject.properties.act` and `context.client_id`. -A fine-grained read request (read access to one customer record) maps to an evaluation such as: - -~~~ json -{ - "subject": { - "type": "user", - "id": "alice@example.com", - "properties": { "act": { "sub": "agent-7" } } - }, - "resource": { "type": "customer_records", "id": "c-4815" }, - "action": { "name": "read" }, - "context": { "client_id": "agent-7", "dpop_jkt": "0ZcOCORZ..." } -} -~~~ +A fine-grained read request (read access to one customer record) maps to an evaluation whose `subject` carries the user and the acting agent, with the record as `resource` and `read` as `action`; the worked trace in {{example-dtr}} shows the full Access Evaluation request and the PDP's response. -A token request may carry several `authorization_details` (and scopes), which the AS evaluates together; it MAY use the AuthZEN Evaluations (bulk) API for this. This profile treats the request as a unit: if any requested authorization is requestable-denied and the client offered `completion_mode=deferred`, the AS defers the whole request and issues the full requested set on approval. Partial issuance (issuing the allowed subset immediately and deferring the rest) is not defined by this profile. +A request may carry several `authorization_details` (and scopes), which the evaluating party evaluates together; it MAY use the AuthZEN Evaluations (bulk) API for this. This profile treats the request as a unit: if any requested authorization is requestable-denied and the client opted into asynchronous completion, the whole request is deferred and the full requested set is issued on approval. Partial issuance (issuing the allowed subset immediately and deferring the rest) is not defined by this profile. ## Requestable denial @@ -228,34 +289,69 @@ When the PDP denies but the denial is requestable, it returns `decision: false` } ~~~ -The AS submits the access request using this binding material, binds the resulting {{ARAP}} task to a `deferral_code`, and returns the DTR deferred response. A `decision: false` with no `context.access_request` is a non-requestable denial and maps to the synchronous OAuth error in the Initial token request rules. - -## Re-evaluation and issuance - -On approval, the AS re-evaluates the same request with the {{ARAP}} `approval` object at `context.approval`: +The example above carries `context.evaluation_id`, which the Access Request Service resolves against the PDP's recorded evaluation; this fits a deployment where the Access Request Service shares the PDP's state. Where the Access Request Service is a separate service from the PDP, it cannot trust the submitting party's claim that a denial occurred, so the denial instead carries a PDP-signed `binding_token` in `context.access_request` that the service verifies cryptographically; `evaluation_id` is then optional: ~~~ json { - "subject": { - "type": "user", - "id": "alice@example.com", - "properties": { "act": { "sub": "agent-7" } } - }, - "resource": { "type": "customer_records", "id": "c-4815" }, - "action": { "name": "read" }, + "decision": false, "context": { - "approval": { "id": "ar_123", "approved_until": "2026-06-13T12:00:00Z" } + "reason": "approval_required", + "access_request": { + "expires_at": "2026-06-13T12:00:00Z", + "binding_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBkcC0xIn0..." + } } } ~~~ -A `decision: true` response authorizes issuance: the AS issues the access token whose granted `authorization_details` reflect the approved `resource` and `action`, with a lifetime bounded by `approved_until`. A `decision: false` at re-evaluation maps to `access_denied` on the next poll; obtaining access then requires a new token request. +The evaluating party submits the access request using this binding material, binds the resulting {{ARAP}} task to a continuation handle, and returns the transport's pending response. A `decision: false` with no `context.access_request` is a non-requestable denial and maps to the synchronous error in {{completion}}. In the transaction challenge binding the resource has already asserted the requestable denial as a signed challenge; the AS validates the challenge and submits the access request from it (and MAY additionally evaluate against its own PDP). + +## Re-evaluation and issuance + +On approval, the evaluating party re-evaluates the same request with the {{ARAP}} `approval` object at `context.approval` (the re-evaluation request, the task it resolves, and the resulting decision are shown in {{example-dtr}}). A `decision: true` response authorizes issuance: the issuer issues the access token whose granted `authorization_details` reflect the approved `resource` and `action`, with a lifetime bounded by `approved_until`. A `decision: false` at re-evaluation maps to `access_denied` on the next poll; obtaining access then requires a new request. + +# Transport Bindings {#bindings} + +Each binding specializes the {{completion}} model onto one OAuth asynchronous mechanism. The completion semantics (issuance-time decision, least privilege, approval-bounded lifetime) are identical; the table summarizes what differs, and the subsections give the detail. + +| | Deferred Token Response | CIBA | Transaction Challenge | +|---|---|---|---| +| Origin of requestable denial | AS (PDP) | AS (PDP) | Protected resource (signed challenge) | +| Entry point | Token endpoint | Backchannel authentication endpoint | Transaction authorization endpoint | +| Client opt-in signal | `completion_mode=deferred` | Use of the backchannel endpoint | `Accept-Txn-Challenge` to the resource | +| Continuation handle | `deferral_code` | `auth_req_id` | `transaction_authorization_id` | +| Completion endpoint | Token endpoint (deferred grant) | Token endpoint, CIBA grant (`poll`, `ping`); client notification endpoint (`push`) | Transaction authorization endpoint | +| Notification (optional) | DTR client callback | CIBA `ping` (`push` delivers the token directly) | none | +| Completion | Access token response | Access token response | Access token response, re-presented to the resource | +| Denial / expiry | `access_denied` / `expired_token` | `access_denied` / `expired_token` | `access_denied` / `expired_token` | + +## Deferred Token Response {#binding-dtr} + +The client makes an ordinary token request and signals deferred completion with `completion_mode=deferred` ({{I-D.gerber-oauth-deferred-token-response}}). The requested authorization is expressed with `authorization_details` ({{RFC9396}}), with `scope`, or with both. The AS maps the request to an AuthZEN Access Evaluation and resolves per {{completion}}. On a requestable denial it binds the access request to a `deferral_code` and returns the DTR deferred response (`authorization_pending` with `deferral_code`, `expires_in`, and `interval`). + +The client polls the token endpoint with the deferred grant type (`urn:ietf:params:oauth:grant-type:deferred`) and the `deferral_code`, and completes per {{completion}}. This binding rides on any originating grant, including Token Exchange {{RFC8693}}, the authorization code grant, and the refresh token grant. + +## Client-Initiated Backchannel Authentication {#binding-ciba} + +CIBA {{OIDC-CIBA}} is purpose-built for decoupled approval: the device that consumes the token (the agent) is separate from the device on which the user authenticates and approves. This profile uses that decoupling for the agentic case in which the principal approves their own agent's elevation out of band, on their own authentication device, while the agent waits. + +In CIBA the user identified by `login_hint`, `login_hint_token`, or `id_token_hint` is the user the AS authenticates and whose identity the issued tokens represent; CIBA does not natively let one party approve a token that acts as a different principal. This binding therefore maps the hint to the principal the issued token acts as, and the approving party is that same principal. Deployments that need a different party (a data owner or manager) to approve should use the DTR or transaction challenge binding, where the approver is the {{ARAP}} workflow's resolver and is unrelated to OAuth authentication. Per {{OIDC-CIBA}} the request carries the `openid` scope; the requested authorization is carried in `authorization_details` (and any additional `scope`), and a `binding_message` MAY convey the operation to the user. + +The AS maps the request to an AuthZEN Access Evaluation and resolves per {{completion}}. On a requestable denial it binds the access request to the CIBA `auth_req_id` and returns it with `expires_in` and (optionally) `interval`. In the `poll` and `ping` delivery modes the client redeems the `auth_req_id` at the token endpoint with the CIBA grant type (`urn:openid:params:grant-type:ciba`); in `ping` the AS first notifies the client's notification endpoint that the result is ready, and in `push` the AS instead delivers the tokens directly to that endpoint with no token-endpoint redemption. A client using `ping` or `push` MUST supply a `client_notification_token` on the backchannel authentication request ({{OIDC-CIBA}}). Where the client polls, polling remains the authoritative completion path as in {{completion}}. In every mode completion is an access token carrying the approved `authorization_details`, bounded by the approval expiry. + +## Transaction Authorization Challenge {#binding-txn} + +In this binding the protected resource is the PEP. When the resource determines that the token presented for an operation does not authorize it, it returns HTTP 401 with a `WWW-Authenticate` challenge carrying a signed `transaction_challenge` (a JWS whose claims include `txn`, `authorization_details`, the resource as `iss`, the AS as `aud`, and a human-readable `reason`), per {{I-D.rosomakho-oauth-txn-challenge}}. The client signals that it understands challenges with the `Accept-Txn-Challenge` header. The challenge is the resource's assertion of a requestable denial, signed so the client cannot forge or broaden the requested operation. + +The client presents the challenge to the AS at the `transaction_authorization_endpoint`. The AS validates the challenge against the resource's signing keys, treats it as the requestable-denial signal (and MAY additionally evaluate against its own PDP), submits the {{ARAP}} access request, and resolves per {{completion}}. On a requestable denial the AS returns a pending response carrying a `transaction_authorization_id`, `expires_in`, `interval`, and an optional `authorization_uri` for interactive approval; the client polls the same endpoint with the `transaction_authorization_id`. + +On approval the AS issues an access token that carries the challenge's `txn` and `authorization_details` and is audience-restricted to the resource. The client re-presents this token to the resource, which validates that the token's `txn` matches the challenge, that the audience identifies the resource, and that the granted `authorization_details` authorize the operation. Because the resource originates and consumes the `txn`, this binding is the one case where completion is not "client holds token, done": the token is evidence re-presented to the resource that originated the challenge. Resources SHOULD treat these tokens as single-use for non-idempotent or high-impact operations. # Examples -These examples are non-normative. The deferral, polling, and completion are identical regardless of which party issues the token; what varies is the originating grant carrying `completion_mode=deferred` and which token-issuing party gates the approval. The examples show approval gated at the AS (Token Exchange, Authorization Code, Refresh Token), at either authorization server in an ID-JAG chain (the IdP that issues the ID-JAG or the RAS that redeems it), and at a resource server that issues its own tokens. All are valid. The first example shows the full flow; the others show the distinctive initial request and name the deferring party. +These examples are non-normative. Together they cover the three PEP placements of {{authzen-integration}}: the AS as PEP (the DTR and CIBA examples, where the AS derives the requestable denial), the resource as PEP (the transaction challenge example, where the resource asserts it), and both (the closing variant of the transaction challenge example, where the AS also applies its own policy). The first example also shows the AS's internal AuthZEN evaluation, marked as off the OAuth wire, so the binding to {{AuthZEN}} and {{ARAP}} is visible end to end; the other examples show only the client-visible OAuth exchange. -## Token Exchange (approval at the AS) +## DTR: Token Exchange (AS as PEP) {#example-dtr} An agent acting on behalf of a user requests fine-grained access to a single protected resource. The `authorization_details` (shown decoded for readability) scopes the request to one record: @@ -275,7 +371,75 @@ grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange &subject_token=...&subject_token_type=... ~~~ -The AS evaluates the request, gets a denied-but-requestable decision, submits an access request, and returns a deferred response: +Internally, and never on the OAuth wire, the AS maps this to an AuthZEN Access Evaluation and calls its PDP: + +~~~ json +{ + "subject": { + "type": "user", + "id": "alice@example.com", + "properties": { "act": { "sub": "agent-7" } } + }, + "resource": { "type": "customer_records", "id": "c-4815" }, + "action": { "name": "read" }, + "context": { "client_id": "agent-7", "dpop_jkt": "0ZcOCORZ..." } +} +~~~ + +The PDP returns a denied-but-requestable decision: + +~~~ json +{ + "decision": false, + "context": { + "evaluation_id": "ev_9f2c8a1b", + "reason": "approval_required", + "access_request": { "expires_at": "2026-06-13T12:00:00Z" } + } +} +~~~ + +Here the Access Request Service shares the PDP's state, so the denial binds by `evaluation_id`; a separate-service Access Request Service would instead receive a PDP-signed `binding_token` ({{authzen-integration}}). The AS submits the {{ARAP}} access request on the agent's behalf, echoing the denial-binding material (authentication to the Access Request Service is omitted for brevity): + +~~~ http +POST /access/v1/requests HTTP/1.1 +Host: pdp.example.com +Content-Type: application/json +Idempotency-Key: 7b8d0f0d-65a1-4af1-9fd3-a684f08a5d14 + +{ + "subject": { + "type": "user", + "id": "alice@example.com", + "properties": { "act": { "sub": "agent-7" } } + }, + "resource": { "type": "customer_records", "id": "c-4815" }, + "action": { "name": "read" }, + "denial": { + "evaluation_id": "ev_9f2c8a1b", + "expires_at": "2026-06-13T12:00:00Z", + "reason": "approval_required" + } +} +~~~ + +The Access Request Service accepts it and returns a pending task: + +~~~ http +HTTP/1.1 202 Accepted +Content-Type: application/json +Location: https://pdp.example.com/access/v1/requests/arq_01HX4Y3AJZ + +{ + "task": { + "id": "arq_01HX4Y3AJZ", + "status": "pending", + "status_endpoint": "https://pdp.example.com/access/v1/requests/arq_01HX4Y3AJZ" + } +} +~~~ + +The AS binds this task to a `deferral_code` and returns the deferred response to the client: ~~~ json { @@ -286,7 +450,7 @@ The AS evaluates the request, gets a denied-but-requestable decision, submits an } ~~~ -The client polls until the record's data owner resolves the request out of band: +The client polls the token endpoint until the record's data owner resolves the request out of band: ~~~ http POST /token HTTP/1.1 @@ -298,7 +462,47 @@ grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adeferred &deferral_code=8d67dc78-7faa-4d41-aabd-67707b374255 ~~~ -On approval the AS re-evaluates and issues an access token carrying the approved authorization, with a lifetime bounded by the approval expiry: +Behind the AS, the data owner approves and the task reaches a terminal state, which the AS reads from the Task Status Endpoint: + +~~~ http +GET /access/v1/requests/arq_01HX4Y3AJZ HTTP/1.1 +Host: pdp.example.com +~~~ + +~~~ http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "task": { "id": "arq_01HX4Y3AJZ", "status": "approved" }, + "result": { + "approval": { "id": "ar_123", "approved_until": "2026-06-13T18:00:00Z" } + } +} +~~~ + +The task completes with the `approval`. Under this profile the completion mode is token issuance, not {{ARAP}}'s `reevaluate` (see {{mapping}}), so the result is the issued token rather than a client-facing `result.mode`; the AS re-evaluates the original request itself with the approval at `context.approval`: + +~~~ json +{ + "subject": { + "type": "user", + "id": "alice@example.com", + "properties": { "act": { "sub": "agent-7" } } + }, + "resource": { "type": "customer_records", "id": "c-4815" }, + "action": { "name": "read" }, + "context": { "approval": { "id": "ar_123", "approved_until": "2026-06-13T18:00:00Z" } } +} +~~~ + +The PDP now allows it: + +~~~ json +{ "decision": true } +~~~ + +The AS issues an access token carrying the approved authorization, with a lifetime bounded by the recorded `approved_until`: ~~~ json { @@ -311,30 +515,37 @@ On approval the AS re-evaluates and issues an access token carrying the approved } ~~~ -If the request is denied or cancelled, the poll returns `access_denied` and no token is issued. +If the data owner denies the request, the task reaches `denied`, the AS's re-evaluation returns `decision: false`, the poll returns `access_denied`, and no token is issued. -## Authorization Code (approval at the AS) +The flow above is independent of the originating grant and of which party gates approval. The same DTR exchange applies when the agent redeems an authorization code (the `authorization_details` bound to the code drive the decision), when it uses a refresh token to reach a resource outside its current scope, and when the gate sits at a resource server that issues its own tokens (contrast {{binding-txn}}, where the resource asserts a signed challenge rather than issuing a token). In an ID-JAG {{?I-D.ietf-oauth-identity-assertion-authz-grant}} chain there are two issuers and approval may be gated at either: the identity provider may defer when it issues the ID-JAG, or the Resource Authorization Server may defer when the agent redeems it. In every case the deferring party returns the same `authorization_pending` response, and the agent polls and completes as shown. -A user authorizes an agent through the authorization code flow, requesting fine-grained authority with `authorization_details` at the authorization endpoint. When the agent exchanges the code, the AS finds the requested action needs manager approval and defers. +## CIBA: decoupled self-approval (AS as PEP) + +An agent acting for Alice needs to export a CRM report, an action that requires Alice's explicit approval. The agent makes a backchannel authentication request naming Alice (the principal the token will act as) with `login_hint`, and conveying the operation to her with `binding_message`; CIBA prompts Alice on her own authentication device while the agent waits: ~~~ http -POST /token HTTP/1.1 +POST /backchannel-authn HTTP/1.1 Host: as.example.com Content-Type: application/x-www-form-urlencoded DPoP: eyJ... -grant_type=authorization_code -&code=SplxlOBeZQQYbYS6WxSbIA -&redirect_uri=https%3A%2F%2Fagent.example%2Fcallback -&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk -&completion_mode=deferred +scope=openid +&login_hint=alice%40example.com +&binding_message=Export+CRM+contacts +&authorization_details=%5B%7B%22type%22%3A%22crm%22%2C%22actions%22%3A%5B%22export%22%5D%2C%22locations%22%3A%5B%22https%3A%2F%2Fcrm.example%2Fcontacts%22%5D%7D%5D ~~~ -The requested `authorization_details` were carried on the authorization request and are bound to the code, so they are not repeated here. The AS returns a deferred response and the agent polls and completes as above. +The AS evaluates the request, gets a denied-but-requestable decision, submits an access request, and returns: -## Refresh Token (approval at the AS) +~~~ json +{ + "auth_req_id": "1c266114-a1be-4252-8ad1-04986c5b9ac1", + "expires_in": 300, + "interval": 5 +} +~~~ -A long-running agent holds a refresh token scoped to one project. When it refreshes to reach a resource outside that scope, the AS defers for the new resource owner's approval rather than issuing immediately. +The agent redeems the `auth_req_id` at the token endpoint once the owner approves out of band: ~~~ http POST /token HTTP/1.1 @@ -342,116 +553,136 @@ Host: as.example.com Content-Type: application/x-www-form-urlencoded DPoP: eyJ... -grant_type=refresh_token -&refresh_token=8xLOxBtZp8 -&completion_mode=deferred -&authorization_details=%5B%7B%22type%22%3A%22documents%22%2C%22actions%22%3A%5B%22read%22%5D%2C%22locations%22%3A%5B%22https%3A%2F%2Ffiles.example%2Fprojects%2Fatlas%2Fspec.pdf%22%5D%7D%5D +grant_type=urn%3Aopenid%3Aparams%3Agrant-type%3Aciba +&auth_req_id=1c266114-a1be-4252-8ad1-04986c5b9ac1 ~~~ -The AS returns a deferred response and the agent polls and completes as above. Any token the AS ultimately issues remains scoped to the approved resource and bounded by the new approval's expiry, and the refresh token cannot be used to broaden or extend it (see {{completion}}). +Before approval the token endpoint returns `authorization_pending`; on approval it returns an access token carrying the approved authorization, bounded by the approval expiry: + +~~~ json +{ + "access_token": "eyJ...", + "token_type": "DPoP", + "expires_in": 900, + "id_token": "eyJ...", + "authorization_details": [ + { "type": "crm", "actions": ["export"], "locations": ["https://crm.example/contacts"] } + ] +} +~~~ -## Identity Assertion Authorization Grant, ID-JAG (approval at the IdP or the RAS) +If Alice declines, or the request expires, the poll returns `access_denied` or `expired_token` and no token is issued. -An ID-JAG flow has two token issuers, and approval may be gated at either. +## Transaction challenge: resource-initiated (RS and both as PEP) -The agent first obtains an ID-JAG from the enterprise identity provider (IdP) by Token Exchange. The **IdP** may defer here, gating issuance of the ID-JAG itself: +An agent already holds a token for a CRM but attempts a high-impact deletion the token does not authorize. The resource returns a signed challenge: ~~~ http -POST /token HTTP/1.1 -Host: idp.acme.example -Content-Type: application/x-www-form-urlencoded -DPoP: eyJ... +DELETE /customers/c-4815 HTTP/1.1 +Host: crm.example +Authorization: DPoP eyJ... +Accept-Txn-Challenge: ?1 +~~~ -grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange -&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aid-jag -&audience=https%3A%2F%2Fcrm.acme.example -&subject_token=...&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aid_token -&completion_mode=deferred -&authorization_details=%5B%7B%22type%22%3A%22crm%22%2C%22actions%22%3A%5B%22export%22%5D%2C%22locations%22%3A%5B%22https%3A%2F%2Fcrm.acme.example%2Fcontacts%22%5D%7D%5D +~~~ http +HTTP/1.1 401 Unauthorized +WWW-Authenticate: DPoP error="transaction_authorization_required", + transaction_challenge="eyJ0eXAiOiJ0eG4tYXV0aHotY2hhbGxlbmdlK2p3dC... (signed JWT)" ~~~ -Alternatively the IdP issues the ID-JAG synchronously and the **Resource Authorization Server (RAS)** defers when the agent redeems it (`grant_type=jwt-bearer`, `assertion`) for an access token: +The agent presents the challenge to the AS: ~~~ http -POST /oauth2/token HTTP/1.1 -Host: crm.acme.example +POST /txn-authorization HTTP/1.1 +Host: as.example.com Content-Type: application/x-www-form-urlencoded DPoP: eyJ... -grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer -&assertion=eyJhbGciOiJFUzI1Ni... (the ID-JAG) -&completion_mode=deferred -&authorization_details=%5B%7B%22type%22%3A%22crm%22%2C%22actions%22%3A%5B%22export%22%5D%2C%22locations%22%3A%5B%22https%3A%2F%2Fcrm.acme.example%2Fcontacts%22%5D%7D%5D +client_id=agent-7 +&transaction_challenge=eyJ0eXAiOiJ0eG4tYXV0aHotY2hhbGxlbmdlK2p3dC... ~~~ -In both placements the deferring party returns the same `authorization_pending` deferred response, and the agent polls and completes the same way. +Here the binding to {{AuthZEN}} differs from the DTR example: the AS does not derive the requestable denial itself, it arrives as the resource-signed challenge. The AS validates the challenge's signature, treats its `authorization_details` and `txn` as the requestable-denial signal (mapping them to an AuthZEN evaluation only if it also applies its own policy, the "both" case below), submits the access request, and returns a pending response: -## Resource Server (approval at the RS) - -Some resources issue their own access tokens, acting as their own authorization server. The agent requests a token directly from the resource, which gates approval itself before issuing: +~~~ json +{ + "transaction_authorization_id": "txn-authz-abc123", + "expires_in": 300, + "interval": 5, + "authorization_uri": "https://as.example.com/txn-authorization/txn-authz-abc123" +} +~~~ -~~~ http -POST /token HTTP/1.1 -Host: files.acme.example -Content-Type: application/x-www-form-urlencoded -DPoP: eyJ... +The agent polls the transaction authorization endpoint with the `transaction_authorization_id`. On approval the AS issues a transaction-scoped token carrying the challenge's `txn`, which the agent re-presents on the original operation: -grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange -&completion_mode=deferred -&authorization_details=%5B%7B%22type%22%3A%22files%22%2C%22actions%22%3A%5B%22delete%22%5D%2C%22locations%22%3A%5B%22https%3A%2F%2Ffiles.acme.example%2Fprojects%2Fq4%22%5D%7D%5D -&subject_token=...&subject_token_type=... +~~~ json +{ + "access_token": "eyJ...", + "token_type": "DPoP", + "expires_in": 120, + "authorization_details": [ + { "type": "customer_records", "actions": ["delete"], "locations": ["https://crm.example/customers/c-4815"] } + ] +} ~~~ -The resource returns a deferred response and the agent polls and completes as above; here the resource server is the party that gates and records the approval. +The resource validates that the token's `txn` matches the challenge and that the granted `authorization_details` authorize the deletion before performing it. + +In this example the resource is the sole PEP and the AS acts only on the validated challenge. A **both as PEP** deployment adds one step: after validating the challenge, the AS additionally evaluates the operation against its own PDP before issuing, so issuance requires both the resource's challenge and an AS allow. If the AS's own evaluation is itself a requestable denial, the same access request resolves both; if it is a non-requestable denial, the AS returns `access_denied` and issues nothing. The wire exchange the agent sees is unchanged. # Mapping to the Access Request and Approval Profile {#mapping} -The OAuth wire surface replaces several {{ARAP}} constructs; the rest map directly. For how the OAuth token request maps to AuthZEN Access Evaluation messages, see {{authzen-integration}}. +The OAuth wire surface replaces several {{ARAP}} constructs; the rest map directly. For how the OAuth request maps to AuthZEN Access Evaluation messages, see {{authzen-integration}}. | ARAP construct | OAuth binding in this profile | |---|---| -| Requestable denial (`context.access_request`) | The AS's internal trigger to defer. Not surfaced to the client. | -| Access request submission | Performed by the AS on the client's behalf during evaluation of the token request. | -| Idempotency key | Best-effort dedup by client and requested authorization (see {{completion}} and Idempotent submission). | -| Task handle | The DTR `deferral_code`. | -| Task Status Endpoint and polling | Polling the token endpoint with the deferred grant type. | -| Callback completion | The optional DTR client callback notification. | -| `approval` object (`context.approval`) | Input to the AS's issuance-time re-evaluation. | +| Requestable denial (`context.access_request`) | The trigger to defer: derived by the AS (DTR, CIBA) or asserted by a protected resource as a signed challenge (transaction challenge). Not surfaced to the client as an AuthZEN object. | +| Access request submission | Performed by the evaluating party on the client's behalf during evaluation. | +| Idempotency key | Best-effort dedup by client and requested authorization, or by challenge `txn` (see {{completion}}). | +| Task handle | The continuation handle: `deferral_code` (DTR), `auth_req_id` (CIBA), or `transaction_authorization_id` (transaction challenge). | +| Task Status Endpoint and polling | Polling the binding's completion endpoint: the token endpoint (DTR, CIBA) or the transaction authorization endpoint. | +| Callback completion | The optional DTR client callback, or CIBA `ping` notification or `push` delivery (both via the client notification endpoint). | +| `approval` object (`context.approval`) | Input to the issuance-time re-evaluation. | | `reevaluate` completion mode | Replaced by token issuance (this profile's completion mode). | -| `binding_token` / `evaluation_id` | Bound to the `deferral_code` in AS state; not exposed to the client. | +| `binding_token` / `evaluation_id` | Bound to the continuation handle in issuer state; not exposed to the client. | | `approved_until` | Upper bound on the issued access token lifetime. | | Denial reason and `next_action` | OAuth error codes (`access_denied`, `expired_token`). | # Sender-Constraining and Cancellation {#sender-constraining} -Per {{I-D.gerber-oauth-deferred-token-response}}, the `deferral_code` and the issued access token are sender-constrained using DPoP {{RFC9449}} or mutual TLS {{RFC8705}}; a public client MUST present DPoP on the initial token request, and every polling and revocation request MUST carry proof of possession for the same key. +The continuation handle and the issued access token are sender-constrained using DPoP {{RFC9449}} or mutual TLS {{RFC8705}}; a public client MUST present DPoP on the initial request, and every polling, redemption, and cancellation request MUST carry proof of possession for the same key. DTR specifies this for the `deferral_code` ({{I-D.gerber-oauth-deferred-token-response}}); the same requirement applies to the CIBA `auth_req_id` and the `transaction_authorization_id` in this profile. The CIBA binding does not have OAuth public clients in this sense: {{OIDC-CIBA}} requires the client to authenticate to the backchannel authentication endpoint with its registered method, and DPoP sender-constrains the `auth_req_id` and issued token on top of that. + +To cancel a pending request: -To cancel a pending request, the client revokes the `deferral_code` at the revocation endpoint {{RFC7009}} (with `token_type_hint` set to `urn:ietf:params:oauth:token-type:deferral-code`, the type defined by {{I-D.gerber-oauth-deferred-token-response}}). The AS MUST cancel the underlying access request. +- **DTR.** The client revokes the `deferral_code` at the revocation endpoint {{RFC7009}} (with `token_type_hint` set to `urn:ietf:params:oauth:token-type:deferral-code`). The AS MUST cancel the underlying access request. +- **CIBA and transaction challenge.** These transports define no client-driven cancellation; the pending request resolves on approval, denial, or expiry of its handle. In all bindings the access request MUST be cancelled when its handle expires. # Privacy Considerations -- The requested and approved `authorization_details` can carry sensitive data (account numbers, amounts, resource identifiers). Parties MUST apply data minimization and protect them in transit and at rest. -- The AS MUST NOT expose the approver's identity, internal approval state, or {{ARAP}} binding material to the client; the client sees only the `deferral_code` and the eventual token or error. -- `error_description` and any human-readable text returned with a deferred or error response MUST be minimized and MUST NOT leak approver identity or internal policy detail, consistent with {{I-D.gerber-oauth-deferred-token-response}}. +- The requested and approved `authorization_details` (and, in the transaction binding, the signed challenge) can carry sensitive data (account numbers, amounts, resource identifiers). Parties MUST apply data minimization and protect them in transit and at rest. +- The evaluating party MUST NOT expose the approver's identity, internal approval state, or {{ARAP}} binding material to the client; the client sees only the continuation handle and the eventual token or error. A CIBA `login_hint` or `binding_message` MUST NOT echo approver detail back to the client beyond what the client supplied. +- `error_description` and any human-readable text returned with a pending or error response MUST be minimized and MUST NOT leak approver identity or internal policy detail. -# Security Considerations +# Security Considerations {#security} -- **A deferred response is not a grant.** `authorization_pending` with a `deferral_code` is not access. Denial remains denial: the AS issues a token only after the issuance-time re-evaluation, and a non-requestable or terminally denied request yields an OAuth error, never a token. +- **A pending response is not a grant.** A pending response carrying a continuation handle is not access. Denial remains denial: a token is issued only after the issuance-time re-evaluation, and a non-requestable or terminally denied request yields an OAuth error, never a token. - **Issuance-time decision, bounded by lifetime.** Because the authoritative decision is made at issuance and the resource server may validate the token offline, deployments MUST bound the access token lifetime by the approval expiry and SHOULD keep lifetimes short and use token introspection where current revocation matters. Refresh tokens and token exchange MUST NOT be used to extend an approval beyond its recorded expiry (see {{completion}}); otherwise the approval bound is defeated. -- **Least privilege and audience.** The issued authorization MUST reflect the approved access and MUST NOT broaden the originating request, and the token SHOULD be audience-restricted to the intended resource using resource indicators {{RFC8707}} so an approved token cannot be replayed at an unintended resource server. -- **Request integrity.** Because the requested `authorization_details` drive a fine-grained authorization decision, deployments SHOULD protect them in transit and from tampering, for example with Pushed Authorization Requests {{?RFC9126}} or a signed request, so the request the AS evaluates is the one the client intended. -- **The deferral code is not authority.** It is opaque, sender-constrained, and bound to the client it was issued to; it is usable only at the token and revocation endpoints. -- **The decision stays behind the AS.** The token request carries a RAR request and the issued token carries the approved grant; neither carries an AuthZEN request or decision object, which keeps the decision engine off the client surface and avoids substitution or replay of decision payloads. +- **Least privilege and audience.** The issued authorization MUST reflect the approved access and MUST NOT broaden the originating request, and the token SHOULD be audience-restricted to the intended resource using resource indicators {{RFC8707}} so an approved token cannot be replayed at an unintended resource server. In the transaction binding the token is audience-restricted to the challenging resource and bound to the challenge `txn`, which the resource validates before acting. +- **Request integrity.** Because the requested `authorization_details` drive a fine-grained authorization decision, deployments SHOULD protect them in transit and from tampering, for example with Pushed Authorization Requests {{?RFC9126}} or a signed request, so the request that is evaluated is the one the client intended. In the transaction binding the challenge is signed by the resource, so the client cannot forge or broaden the requested operation; the AS MUST validate the challenge signature against the resource's keys before acting on it. +- **The continuation handle is not authority.** It is opaque, sender-constrained, and bound to the client it was issued to; it is usable only at the binding's completion (and, for DTR, revocation) endpoint. +- **The decision is not carried on the wire.** The request carries a RAR request (or a resource-signed challenge) and the issued token carries the approved grant; neither carries an AuthZEN request or decision object, which keeps the decision engine off the client surface and avoids substitution or replay of decision payloads. # Relationship to Other Specifications {#relationship} -- {{ARAP}}: this profile is its OAuth token issuance completion mode. {{ARAP}} owns the requestable denial, access request, approval, and audit lifecycle; this profile binds completion to OAuth token issuance. -- {{I-D.gerber-oauth-deferred-token-response}}: the asynchronous transport. This profile adds no new grant type; it uses DTR's `completion_mode`, deferred grant, and polling. -- {{RFC9396}}: carries the requested authorization in the token request and the approved authorization in the issued token. +- {{ARAP}}: this profile is its OAuth token issuance completion mode. {{ARAP}} owns the requestable denial, access request, approval, and audit lifecycle; this profile binds completion to OAuth token issuance and supports the AS, a protected resource, or both as the PEP. +- {{I-D.gerber-oauth-deferred-token-response}}: an asynchronous transport for this profile. It adds no new grant type; the binding uses DTR's `completion_mode`, deferred grant, and polling. +- {{OIDC-CIBA}}: an asynchronous transport built for decoupled approval, where the approving party need not be the client; the binding uses the backchannel endpoint, the CIBA grant, and `auth_req_id`. +- {{I-D.rosomakho-oauth-txn-challenge}}: a resource-initiated asynchronous transport in which the protected resource asserts the requestable denial as a signed challenge; the binding uses the transaction authorization endpoint and the `txn`-bound token. +- {{RFC9396}}: carries the requested authorization in the request and the approved authorization in the issued token. - {{RFC8693}}: Token Exchange is a natural originating grant for on-behalf-of agent flows, and DTR can defer it; this profile composes with it unchanged. -- {{?I-D.brossard-oauth-rar-authzen}}: that draft places an AuthZEN request and response inside `authorization_details`. This profile deliberately does not. The decision stays behind the AS and the token carries the approved grant, not the evaluation. -- {{?RFC8628}}: the Device Authorization Grant solves a related asynchronous case for the same end user; DTR (and therefore this profile) does not assume the approver is the requesting user, which is the agentic case this profile targets. +- {{?I-D.brossard-oauth-rar-authzen}}: that draft places an AuthZEN request and response inside `authorization_details`. This profile deliberately does not. The decision stays behind the evaluating party and the token carries the approved grant, not the evaluation. +- {{?RFC8628}}: the Device Authorization Grant solves a related asynchronous case for the same end user; these transports (and therefore this profile) do not assume the approver is the requesting user, which is the agentic case this profile targets. # IANA Considerations -This profile defines no new OAuth grant types, parameters, or error codes; it reuses {{I-D.gerber-oauth-deferred-token-response}} and {{RFC9396}}. It requests no IANA registrations. Any `authorization_details` type used by a deployment is registered per {{RFC9396}}. +This profile defines no new OAuth grant types, parameters, endpoints, or error codes; it reuses {{I-D.gerber-oauth-deferred-token-response}}, {{OIDC-CIBA}}, {{I-D.rosomakho-oauth-txn-challenge}}, and {{RFC9396}}. It requests no IANA registrations. Any `authorization_details` type used by a deployment is registered per {{RFC9396}}. From 6d35e6490c70dfc375883286f7789b25a3cce286 Mon Sep 17 00:00:00 2001 From: Karl McGuinness Date: Sat, 4 Jul 2026 11:59:36 -0700 Subject: [PATCH 3/6] Clarify binding isolation and internal ARS submission Address PR review: a client engages exactly one binding and the evaluating party never converts a request made under one binding into another. The ARAP access request is an internal submission to the Access Request Service, not an outbound OAuth request; the continuation handle and pending response are always the same transport the request was made under. In the transaction challenge binding the client itself presents the challenge to the AS named in the challenge aud and authenticates there, so the originating deployment crosses no server-to-server credential. --- .../authzen-access-request-oauth-profile-1_0.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md b/profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md index 714b64b..7aa5621 100644 --- a/profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md +++ b/profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md @@ -214,11 +214,11 @@ This section defines the resolution outcomes and completion semantics shared by ## Resolution outcomes -The evaluating party resolves an asynchronous authorization request as follows: +A client engages exactly one binding, selected by the entry point and opt-in it used; the evaluating party never converts a request made under one binding into another. The evaluating party resolves an asynchronous authorization request as follows: - **Allow.** The transport's normal success response is returned synchronously (a token, for the request mechanisms that issue one directly). - **Deny, not requestable.** A synchronous error is returned in the transport's error form: at the token endpoint, `invalid_authorization_details` ({{RFC9396}}) when the denial concerns the requested `authorization_details`, `invalid_scope` when it concerns requested `scope`, and otherwise `invalid_grant`. The pending and terminal-denial codes (`authorization_pending`, `access_denied`, `expired_token`, `slow_down`) are reserved for the deferred path and MUST NOT be returned here. -- **Deny, requestable.** The evaluating party MUST NOT issue a token. It submits an {{ARAP}} access request on the client's behalf, binds it to a continuation handle, and returns the transport's pending response (carrying the handle, `expires_in`, and `interval`). +- **Deny, requestable.** The evaluating party MUST NOT issue a token. It submits an {{ARAP}} access request to its internal Access Request Service on the client's behalf (an internal approval submission, not an outbound OAuth request to any authorization server), binds the resulting task to this transport's continuation handle, and returns this transport's pending response (carrying the handle, `expires_in`, and `interval`). - **Deny, requestable, but the client did not opt into asynchronous completion.** Where a binding requires the client to signal that it can handle a deferred outcome (DTR's `completion_mode=deferred`), and the client did not, the evaluating party MUST NOT defer. It returns the same synchronous denial error it would for a non-requestable denial; the requestable nature is not exposed. A client that wishes to obtain access through approval MUST opt in. ## Idempotent submission @@ -343,7 +343,7 @@ The AS maps the request to an AuthZEN Access Evaluation and resolves per {{compl In this binding the protected resource is the PEP. When the resource determines that the token presented for an operation does not authorize it, it returns HTTP 401 with a `WWW-Authenticate` challenge carrying a signed `transaction_challenge` (a JWS whose claims include `txn`, `authorization_details`, the resource as `iss`, the AS as `aud`, and a human-readable `reason`), per {{I-D.rosomakho-oauth-txn-challenge}}. The client signals that it understands challenges with the `Accept-Txn-Challenge` header. The challenge is the resource's assertion of a requestable denial, signed so the client cannot forge or broaden the requested operation. -The client presents the challenge to the AS at the `transaction_authorization_endpoint`. The AS validates the challenge against the resource's signing keys, treats it as the requestable-denial signal (and MAY additionally evaluate against its own PDP), submits the {{ARAP}} access request, and resolves per {{completion}}. On a requestable denial the AS returns a pending response carrying a `transaction_authorization_id`, `expires_in`, `interval`, and an optional `authorization_uri` for interactive approval; the client polls the same endpoint with the `transaction_authorization_id`. +The client itself presents the challenge to the AS named in the challenge's `aud` at that AS's `transaction_authorization_endpoint`, authenticating to that endpoint per {{I-D.rosomakho-oauth-txn-challenge}}; the originating deployment does not forward anything on the client's behalf, so no server-to-server credential is crossed even when `aud` names an AS the client has not used before. That AS validates the challenge against the resource's signing keys, treats it as the requestable-denial signal (and MAY additionally evaluate against its own PDP), submits the {{ARAP}} access request to its own Access Request Service, and resolves per {{completion}}. On a requestable denial the AS returns a pending response carrying a `transaction_authorization_id`, `expires_in`, `interval`, and an optional `authorization_uri` for interactive approval; the client polls the same endpoint with the `transaction_authorization_id`. On approval the AS issues an access token that carries the challenge's `txn` and `authorization_details` and is audience-restricted to the resource. The client re-presents this token to the resource, which validates that the token's `txn` matches the challenge, that the audience identifies the resource, and that the granted `authorization_details` authorize the operation. Because the resource originates and consumes the `txn`, this binding is the one case where completion is not "client holds token, done": the token is evidence re-presented to the resource that originated the challenge. Resources SHOULD treat these tokens as single-use for non-idempotent or high-impact operations. From 9cd1e3036d54cc9096876abfabd72b2af1e74931 Mon Sep 17 00:00:00 2001 From: Karl McGuinness Date: Sat, 4 Jul 2026 20:13:42 -0700 Subject: [PATCH 4/6] Frame opt-in as transport requirement; surface requestable denials as remediation Address PR review on the non-opt-in path: - Opt-in (DTR completion_mode=deferred) is a requirement of the deferral transport guarding the deferred response, not a precondition for approval; the internal access request and re-evaluation need no client opt-in. - Rather than silently deny, the evaluating party MAY surface a requestable denial as remediation, and the client proceeds as it sees fit: retry opting into DTR/CIBA, reuse an existing token, or pursue interactive end-user approval. A requestable denial does not by itself imply deferral. Disclosure stays deployment-policy-gated. - Add informative reference to draft-zehavi-oauth-rar-metadata as the resource-initiated discovery/remediation front-end that composes with this profile (what to request, transport carries the wait, token carries the fulfillment); the unsigned counterpart to the signed Transaction Authorization Challenge. --- .../authzen-access-request-oauth-profile-1_0.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md b/profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md index 7aa5621..949c074 100644 --- a/profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md +++ b/profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md @@ -219,7 +219,7 @@ A client engages exactly one binding, selected by the entry point and opt-in it - **Allow.** The transport's normal success response is returned synchronously (a token, for the request mechanisms that issue one directly). - **Deny, not requestable.** A synchronous error is returned in the transport's error form: at the token endpoint, `invalid_authorization_details` ({{RFC9396}}) when the denial concerns the requested `authorization_details`, `invalid_scope` when it concerns requested `scope`, and otherwise `invalid_grant`. The pending and terminal-denial codes (`authorization_pending`, `access_denied`, `expired_token`, `slow_down`) are reserved for the deferred path and MUST NOT be returned here. - **Deny, requestable.** The evaluating party MUST NOT issue a token. It submits an {{ARAP}} access request to its internal Access Request Service on the client's behalf (an internal approval submission, not an outbound OAuth request to any authorization server), binds the resulting task to this transport's continuation handle, and returns this transport's pending response (carrying the handle, `expires_in`, and `interval`). -- **Deny, requestable, but the client did not opt into asynchronous completion.** Where a binding requires the client to signal that it can handle a deferred outcome (DTR's `completion_mode=deferred`), and the client did not, the evaluating party MUST NOT defer. It returns the same synchronous denial error it would for a non-requestable denial; the requestable nature is not exposed. A client that wishes to obtain access through approval MUST opt in. +- **Deny, requestable, but the client did not opt into asynchronous completion.** A binding that guards its deferred response behind a client signal (DTR's `completion_mode=deferred`) MUST NOT return that deferred response when the client did not send it, because the client may be unable to handle it. Opting in is a requirement of the deferral *transport*, not a precondition for approval; the internal access request and re-evaluation need no client opt-in. Rather than silently deny, the evaluating party MAY surface the requestable denial to the client as remediation so the client can proceed as it sees fit: retry while opting into a deferral transport (DTR or CIBA), reuse an existing token that already satisfies the requirement, or pursue interactive end-user approval where deferral is not needed. A requestable denial therefore does not by itself imply deferral. Where the requestable denial originates at a protected resource, that remediation is delivered to the client directly as a challenge (a signed Transaction Authorization Challenge, or an `authorization_remediation` response {{?I-D.zehavi-oauth-rar-metadata}}), with no upfront opt-in. A deployment MAY withhold the requestable nature where revealing that the operation is obtainable through approval is itself sensitive. ## Idempotent submission @@ -681,6 +681,7 @@ To cancel a pending request: - {{RFC9396}}: carries the requested authorization in the request and the approved authorization in the issued token. - {{RFC8693}}: Token Exchange is a natural originating grant for on-behalf-of agent flows, and DTR can defer it; this profile composes with it unchanged. - {{?I-D.brossard-oauth-rar-authzen}}: that draft places an AuthZEN request and response inside `authorization_details`. This profile deliberately does not. The decision stays behind the evaluating party and the token carries the approved grant, not the evaluation. +- {{?I-D.zehavi-oauth-rar-metadata}}: a resource-initiated remediation mechanism in which a protected resource returns `insufficient_authorization` with an `authorization_remediation` challenge carrying the `authorization_details` the client must obtain, plus an optional `authorization_reference` the client can use to locate a token it already holds that satisfies the requirement (avoiding a new grant). It composes with this profile as a discovery and remediation front-end: the client either reuses a matching token or takes the remediation `authorization_details` to the AS while opting into deferred completion (or using CIBA); the AS resolves the requestable denial through approval and issues a token carrying the granted `authorization_details` as the fulfillment. Three layers compose: this mechanism says what to request, a transport binding carries the approval wait, and the issued token carries the fulfillment. It is the unsigned, fresh-grant counterpart to the resource-signed Transaction Authorization Challenge; because the remediation challenge is not a signed requestable-denial assertion, the AS evaluates the request itself (AS as PEP) rather than consuming the resource's decision. - {{?RFC8628}}: the Device Authorization Grant solves a related asynchronous case for the same end user; these transports (and therefore this profile) do not assume the approver is the requesting user, which is the agentic case this profile targets. # IANA Considerations From e3533e62267f32214d01abf4721908922968c550 Mon Sep 17 00:00:00 2001 From: Karl McGuinness Date: Sat, 4 Jul 2026 20:14:42 -0700 Subject: [PATCH 5/6] Acknowledge Yaron Zehavi --- .../authzen-access-request-oauth-profile-1_0.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md b/profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md index 949c074..01b1a35 100644 --- a/profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md +++ b/profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md @@ -687,3 +687,9 @@ To cancel a pending request: # IANA Considerations This profile defines no new OAuth grant types, parameters, endpoints, or error codes; it reuses {{I-D.gerber-oauth-deferred-token-response}}, {{OIDC-CIBA}}, {{I-D.rosomakho-oauth-txn-challenge}}, and {{RFC9396}}. It requests no IANA registrations. Any `authorization_details` type used by a deployment is registered per {{RFC9396}}. + +--- back + +# Acknowledgements + +The author thanks Yaron Zehavi for review and discussion, including how this profile composes with the remediation mechanism of {{?I-D.zehavi-oauth-rar-metadata}}. From 483abc2b91b0984e6a47a6ec94df85c7a7c2dfd5 Mon Sep 17 00:00:00 2001 From: Karl McGuinness Date: Sat, 4 Jul 2026 20:29:46 -0700 Subject: [PATCH 6/6] Make the two completion modes first-class; rework refresh guidance Address PR review on standing vs single-use access: - Recast the Authority-model design goal from a one-way tradeoff into two first-class completion modes a deployment chooses per approval: re-evaluation completion (ARAP reevaluate, per-call currency, no long-lived credential) and token-issuance completion (this profile, grant carried in the token, issuance-time decision bounded by lifetime). Reworded the adjacent goal to 'Shared completion semantics, several transports' to avoid a model/mode clash. - Rework the Approved refresh guidance: keep the invariant (no access token, refresh token, or exchanged token outlives approved_until or broadens the grant); under token-issuance completion, standing time-bounded approvals (path read, policy-granted privileges as RAR) are served by a refresh bounded by approved_until and re-evaluated at each refresh; single-use approvals (single payment, single-use Transaction Authorization Challenge) get none; per-call currency is re-evaluation completion, out of scope. --- .../authzen-access-request-oauth-profile-1_0.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md b/profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md index 01b1a35..86faa51 100644 --- a/profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md +++ b/profiles/authzen-access-request-oauth/authzen-access-request-oauth-profile-1_0.md @@ -134,10 +134,14 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S - **OAuth-native completion.** In this mode the issued access token is the decision representation. Authorization is exercised by presenting the token to a resource server, not by carrying an AuthZEN decision object on the wire. - **Compose, do not reinvent.** The asynchronous transport is an existing OAuth mechanism (DTR, CIBA, or the transaction challenge), the fine-grained request and grant are RAR, and the approval lifecycle is {{ARAP}}. This profile only binds them; it defines no new endpoints, grant types, or parameters of its own. -- **One completion model, several transports.** The resolution outcomes, the issuance-time decision, the least-privilege constraint, and the approval-bounded lifetime are defined once and shared by all three bindings (see {{completion}}). A binding section specializes only the transport surface: how the request enters, the name of the continuation handle, where the client polls or is notified, and how completion is delivered (see {{bindings}}). +- **Shared completion semantics, several transports.** The resolution outcomes, the issuance-time decision, the least-privilege constraint, and the approval-bounded lifetime are defined once and shared by all three bindings (see {{completion}}). A binding section specializes only the transport surface: how the request enters, the name of the continuation handle, where the client polls or is notified, and how completion is delivered (see {{bindings}}). - **The decision is not carried on the wire.** The request carries a RAR request (or, in the transaction challenge binding, a resource-signed challenge), the evaluating party maps it to an AuthZEN evaluation internally, and the issued token carries the approved grant. No AuthZEN request or decision object travels between the client and the AS (see {{relationship}}). - **Least privilege.** The issued token's granted authorization (`authorization_details`, and `scope` if used) reflects the approved access and MUST NOT broaden the originating request. The token SHOULD be audience-restricted to the intended resource using resource indicators {{RFC8707}}, so a narrowly approved token is not presentable at an unintended resource server. -- **Authority model: an issuance-time decision, bounded by token lifetime.** {{ARAP}}'s base model keeps the PDP authoritative at enforcement through per-call re-evaluation. A token issuance completion mode necessarily moves the authoritative decision to issuance time: the AS evaluates, issues a bearer token, and a resource server later validates that token, possibly offline. This profile therefore trades per-call re-evaluation for an issuance-time decision bounded by the token's lifetime. Deployments preserve currency with short token lifetimes and token introspection, the issued access token's lifetime MUST NOT exceed the approval expiry the Access Request Service recorded, and the approval bounds the whole credential rather than a single token (see {{completion}}). +- **Authority model: two completion modes, chosen per approval.** Approval can complete in one of two ways, and a deployment selects per approval and MAY mix them: + - *Re-evaluation completion* ({{ARAP}}'s `reevaluate`): the PDP stays authoritative at enforcement, re-evaluated on every call. Currency is per call and no long-lived credential is issued. + - *Token-issuance completion* (this profile): the approved grant is carried in an issued access token that is itself the decision representation. The authoritative decision is made at issuance time and a resource server later validates the token, possibly offline, so currency is bounded by the token's lifetime. + + This profile defines token-issuance completion; it trades per-call re-evaluation for an issuance-time decision. Deployments preserve currency with short token lifetimes, token introspection, and re-evaluation at each refresh; the issued access token's lifetime MUST NOT exceed the approval expiry the Access Request Service recorded; and the approval bounds the whole credential rather than a single token (see {{completion}}). # Terminology @@ -235,7 +239,9 @@ The client redeems the continuation handle at the binding's completion endpoint, - **Approved.** The issuer issues the access token. The granted authorization (`authorization_details`, and `scope` if used) carries the approved access, which MUST NOT broaden the originating request, and the access token lifetime MUST NOT exceed the recorded approval expiry. - The approval bounds the credential, not a single token. In this completion mode the issuer SHOULD NOT issue a refresh token; if it does, the refresh token and every access token derived from it MUST be bounded by the recorded approval expiry, and a refresh after that expiry MUST fail with `invalid_grant` and requires a new access request. Likewise, a token issued by this profile MUST NOT be exchanged ({{RFC8693}}) into a credential that is longer-lived or broader than the approval. + The approval bounds the credential, not a single token: no access token, refresh token, or token obtained by exchange ({{RFC8693}}) may outlive the recorded approval expiry (`approved_until`) or broaden the approved access, and a refresh or exchange after that expiry MUST fail with `invalid_grant` and requires a new access request. + + Within that bound, whether a refresh token applies depends on the completion mode and on what was approved. Under token-issuance completion, standing, time-bounded access (for example read access to a path, or a policy-granted set of privileges expressed as RAR) is served by a refresh token bounded by `approved_until` and re-evaluated at each refresh, which keeps the PDP in the loop within the approval window; a single-use approval (for example a single payment, or the single-use Transaction Authorization Challenge) SHOULD NOT yield a refresh token. Where standing access should instead be re-evaluated on every call, that is re-evaluation completion ({{ARAP}}'s `reevaluate`), which needs no long-lived credential and is out of scope for this profile. - **Terminal denial, cancellation, or expiry.** The issuer returns `access_denied` (or `expired_token` once the handle lifetime has elapsed). The issued access token is the decision representation. This is the token issuance completion mode that {{ARAP}} defers to a downstream profile. Because completion is the issued token, this mode is not surfaced to the client as an {{ARAP}} `result.mode` value; the OAuth token response is the result.