diff --git a/.github/workflows/jekyll-gh-pages.yml b/.github/workflows/jekyll-gh-pages.yml index 67869b5e..c6e707a0 100644 --- a/.github/workflows/jekyll-gh-pages.yml +++ b/.github/workflows/jekyll-gh-pages.yml @@ -37,6 +37,18 @@ jobs: - name: access-request-approval-profile source: profiles/authzen-access-request-approval/authzen-access-request-approval-profile-1_0.md html: authzen-access-request-approval-profile-1_0.html + - name: access-request-catalog-profile + source: profiles/authzen-access-request-approval/authzen-access-request-catalog-profile-1_0.md + html: authzen-access-request-catalog-profile-1_0.html + - name: access-request-bulk-profile + source: profiles/authzen-access-request-approval/authzen-access-request-bulk-profile-1_0.md + html: authzen-access-request-bulk-profile-1_0.html + - name: access-request-callback-profile + source: profiles/authzen-access-request-approval/authzen-access-request-callback-profile-1_0.md + html: authzen-access-request-callback-profile-1_0.html + - name: access-request-actor-profile + source: profiles/authzen-access-request-approval/authzen-access-request-actor-profile-1_0.md + html: authzen-access-request-actor-profile-1_0.html - name: obligations-profile source: profiles/authzen-obligations-profile-1_0.md html: authzen-obligations-profile-1_0.html diff --git a/README.md b/README.md index a0698443..1f527c75 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,10 @@ COAZ-MCP is the COAZ binding for the Model Context Protocol (MCP), defining how A profile that specifies an approval workflow for handling denials in a structured way. The HTML version is available [here](https://openid.github.io/authzen/authzen-access-request-approval-profile-1_0.html) +A companion [Access Request Catalog Profile](https://openid.github.io/authzen/authzen-access-request-catalog-profile-1_0.html) defines how request form fields backed by catalogs (applications, entitlements, roles) are described and resolved. + +Companion [Bulk Access Requests](https://openid.github.io/authzen/authzen-access-request-bulk-profile-1_0.html) and [Callback Notifications](https://openid.github.io/authzen/authzen-access-request-callback-profile-1_0.html) profiles define multi-item requests and authenticated completion notifications, and an [Actor Delegation](https://openid.github.io/authzen/authzen-access-request-actor-profile-1_0.html) profile defines how a PEP conveys the acting party and request origin. + ## Draft Obligations Profile A profile that lets a PDP attach mandatory, machine-readable actions -- obligations -- to an authorization decision, which the PEP must carry out in order to honor that decision. It defines the obligation object model, PEP compliance semantics, a set of normative obligation types, and a mechanism for a PDP and PEP to discover which types they mutually support. It is at `profiles/authzen-obligations-profile-1_0.md`. The HTML version is available [here](https://openid.github.io/authzen/authzen-obligations-profile-1_0.html). diff --git a/profiles/Makefile b/profiles/Makefile index 254d878d..f60b42b9 100644 --- a/profiles/Makefile +++ b/profiles/Makefile @@ -22,9 +22,7 @@ all: @ $(MAKE) authzen-coaz-mcp-binding-1_0.xml @ $(MAKE) authzen-coaz-mcp-binding-1_0.html @ $(MAKE) authzen-coaz-mcp-binding-1_0.txt - @ $(MAKE) authzen-access-request-approval/authzen-access-request-approval-profile-1_0.xml - @ $(MAKE) authzen-access-request-approval/authzen-access-request-approval-profile-1_0.html - @ $(MAKE) authzen-access-request-approval/authzen-access-request-approval-profile-1_0.txt + @ $(MAKE) -C authzen-access-request-approval all @ $(MAKE) authzen-oauth/authzen-oauth-token-issuance-1_0.xml @ $(MAKE) authzen-oauth/authzen-oauth-token-issuance-1_0.html @ $(MAKE) authzen-oauth/authzen-oauth-token-issuance-1_0.txt diff --git a/profiles/authzen-access-request-approval/Makefile b/profiles/authzen-access-request-approval/Makefile index 9811e806..13f8dfa2 100644 --- a/profiles/authzen-access-request-approval/Makefile +++ b/profiles/authzen-access-request-approval/Makefile @@ -1,25 +1,29 @@ -OPEN=$(word 1, $(wildcard /usr/bin/xdg-open /usr/bin/open /bin/echo)) -SOURCES?=${wildcard *.xml */*.xml} -TEXT=${SOURCES:.xml=.txt} -HTML=${SOURCES:.xml=.html} +.DEFAULT_GOAL := all -text: $(TEXT) -html: $(HTML) +# Explicit sources exclude working copies and editorial documents. +SOURCES ?= authzen-access-request-approval-profile-1_0.md \ + authzen-access-request-catalog-profile-1_0.md \ + authzen-access-request-bulk-profile-1_0.md \ + authzen-access-request-callback-profile-1_0.md \ + authzen-access-request-actor-profile-1_0.md +XML = $(SOURCES:.md=.xml) +HTML = $(SOURCES:.md=.html) +TEXT = $(SOURCES:.md=.txt) -%.html: %.xml - xml2rfc --html $^ +.PHONY: all xml html text +.DELETE_ON_ERROR: +.SECONDARY: $(XML) -%.txt: %.xml - xml2rfc $^ +all: xml html text +xml: $(XML) +html: $(HTML) +text: $(TEXT) %.xml: %.md - kramdown-rfc2629 > $@ $^ + kramdown-rfc2629 $< > $@ -all: - @ $(MAKE) authzen-mcp-profile-1_0.xml - @ $(MAKE) authzen-mcp-profile-1_0.html - @ $(MAKE) authzen-mcp-profile-1_0.txt - @ $(MAKE) authzen-access-request-approval/authzen-access-request-approval-profile-1_0.xml - @ $(MAKE) authzen-access-request-approval/authzen-access-request-approval-profile-1_0.html - @ $(MAKE) authzen-access-request-approval/authzen-access-request-approval-profile-1_0.txt +%.html: %.xml + xml2rfc --html $< -o $@ +%.txt: %.xml + xml2rfc --text $< -o $@ diff --git a/profiles/authzen-access-request-approval/authzen-access-request-actor-profile-1_0.md b/profiles/authzen-access-request-approval/authzen-access-request-actor-profile-1_0.md new file mode 100644 index 00000000..8c90e911 --- /dev/null +++ b/profiles/authzen-access-request-approval/authzen-access-request-actor-profile-1_0.md @@ -0,0 +1,187 @@ +--- +title: "AuthZEN Actor Delegation Profile - Draft 1" +abbrev: "ARAP Actor" +category: std +ipr: none + +docname: authzen-access-request-actor-profile-1_0 +workgroup: OpenID AuthZEN +consensus: true +v: 3 +stand_alone: true +pi: [toc, sortrefs, symrefs, private] +keyword: + - authorization + - access request + - delegation + - agents + +author: + - + name: Karl McGuinness + org: Independent + email: public@karlmcguinness.com + +normative: + RFC8693: + ARAP: + title: "AuthZEN Access Request and Approval Profile 1.0" + 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 profile defines how a Policy Enforcement Point that acts on behalf of upstream principals conveys the acting party and the origin of an Access Request under the AuthZEN Access Request and Approval Profile, and how the Access Request Service verifies a claimed actor chain before using it. + +--- middle + +# Introduction + +This companion to the AuthZEN Access Request and Approval Profile {{ARAP}} defines the `client.actor` and `client.source` members of an Access Request submission, the delegation model they express, and the verification an Access Request Service applies to a claimed actor chain. The base profile's rules that reference these members remain there: the PEP preserves the actor identity when it submits, the Access Request Service does not treat unverified actor content as authorization input, and structural comparison excludes `subject.properties.act` because the PEP may carry the actor here instead. + +# Requirements Notation and Conventions + +{::boilerplate bcp14-tagged} + +The terms PEP, PDP, Subject, Resource, Action, Context, Access Request, and Access Request Service are used as defined by {{ARAP}}. + +# Delegation and Acting Parties {#delegation} + +A PEP often acts for upstream principals: an application for a user, an Authorization Server for a client and user, an agent runtime for an agent and user, or a Security Token Service for an upstream caller. + +This profile does not define a new Subject shape for actor delegation. Implementations SHOULD follow the conventions defined in {{?I-D.mcguinness-oauth-actor-profile}}, which standardizes an `act` claim representing the immediate actor with required `sub` and `iss` members and a RECOMMENDED `sub_profile` member (taking values such as `ai_agent`, `service`, or `user`). Nested `act` objects represent multi-hop delegation chains. The canonical actor identifier is the (`iss`, `sub`) pair regardless of which carrier expresses it. + +Under this profile: + +* The AuthZEN Authorization API `subject` carries the principal on whose behalf the operation is performed. +* `client.actor` (defined in {{client-actor-source}}) carries the immediate actor and MAY include a nested `act` claim that walks the delegation chain from the immediate actor outward toward the Subject. + +Approval routing at the Access Request Service MAY consider any identity in the chain (for example, routing approval to the principal's owner, the agent's deployment owner, or a delegated approver). This profile otherwise leaves routing policy unconstrained; it requires that the necessary identities be representable in the submission and verifiable by the service before routing decisions are taken. + +Cross-implementation interoperability for delegated flows depends on adoption of a common actor convention. Deployments and profiles that depend on a specific actor convention SHOULD document the Subject shape, the actor convention used, and the credential format the Access Request Service accepts as proof of the chain. + +## Client Actor and Source {#client-actor-source} + +The `client` object of an Access Request submission ([Additional Request Members](https://openid.github.io/authzen/authzen-access-request-approval-profile-1_0.html#submission-additional-information)) has two further members: + + * `actor`: OPTIONAL. Object identifying the immediate actor on whose behalf the PEP submits the Access Request, when that actor differs from the Subject or when the deployment needs to audit the actor separately. The following members are defined; implementations MAY include additional members. + * `id`: REQUIRED. String. Stable identifier for the actor. + * `issuer`: OPTIONAL. String. Issuer, authority, tenant, or identity provider for the actor identifier. + * `type`: OPTIONAL. String. Actor category, such as `user`, `service`, `workload`, or `ai_agent`. + * `act`: OPTIONAL. Object. Nested actor representing the next link in a delegation chain, following the conventions in {{?I-D.mcguinness-oauth-actor-profile}}. Each `act` carries `sub` and `iss` (corresponding to `id` and `issuer` in the immediate actor) and optionally `sub_profile`; nesting represents the chain from the immediate actor outward toward the Subject. See {{delegation}}. + * `source`: OPTIONAL. Object. Audit-trail context describing where the request originated. The following members are defined; implementations MAY include additional members. + * `session_id`: OPTIONAL. String. Identifier of a bounded interaction context that produced the request, such as a chat or agent conversation, a web or mobile application session, a CLI invocation, or a long-running workflow thread. This is an audit-origin identifier and is distinct from any authentication or authorization session associated with the caller. + * `external_url`: OPTIONAL. HTTPS URI. URL of an external system (ticket, document, dashboard, chat thread) that motivated the request. + * `integration_id`: OPTIONAL. String. Identifier of an upstream integration or workflow that produced the request. + +## Actor Chain Verification {#actor-source-verification} + +When authenticating a submission, the Access Request Service: + +* When the submission claims an actor or actor chain in `client.actor`, MUST verify that the authenticated caller's credential authorizes the entire claimed chain, not only the immediate actor. Mechanisms commonly used to provide such authorization include {{RFC8693}} OAuth 2.0 Token Exchange (where the access token names the Subject as the on-behalf-of party and the chain via `act` claims), signed assertions from a trusted issuer, or deployment-specific authentication policies. +* MUST reject submissions whose claimed chain cannot be verified against the caller's credential or against trusted issuers identified in the deployment. + +Unverified `client.actor` content MAY be retained as audit metadata only; the rule that it is not authorization input is stated in the base profile's [Submission Processing](https://openid.github.io/authzen/authzen-access-request-approval-profile-1_0.html#submission-processing). + +# Interaction with the Base Profile + +The base profile states three rules that reference the members defined here; this profile does not restate them. + +* A PEP preserves the actor identity conveyed in the denied evaluation's Subject, either in the submission's `subject` or normalized to `client.actor` ([Construct the Submission](https://openid.github.io/authzen/authzen-access-request-approval-profile-1_0.html#pep-construct)). +* An Access Request Service does not rely on `client.actor` or `client.source` as authorization input unless it has independently verified them ([Submission Processing](https://openid.github.io/authzen/authzen-access-request-approval-profile-1_0.html#submission-processing)). +* Structural comparison for denial binding and approval scope excludes `subject.properties.act` ([Structural Comparison](https://openid.github.io/authzen/authzen-access-request-approval-profile-1_0.html#structural-comparison)). + +# Security Considerations + +**PEP acting on behalf of the Subject.** Accepting unverified actor claims would let a PEP assert authority it cannot demonstrate. {{actor-source-verification}} governs chain verification; the base profile's endpoint protection requires caller authorization to submit or view the request for the supplied Subject, Resource, and Action. + +The identity-binding questions the base profile records for the caller, requester, and client apply to the acting party as well; this profile does not resolve them. + +# Privacy Considerations + +The privacy considerations of {{ARAP}} apply. `client.source` identifiers (`session_id`, `external_url`, `integration_id`) can link an Access Request to a conversation, application session, or external record; deployments populate them where audit policy requires and protect them as they protect approver and workflow details. + +# IANA Considerations + +This specification makes no requests of IANA. + +# OpenID Foundation Registry Considerations + +## AuthZEN Access Request Member Names Registry {#member-names} + +This specification registers the following entries in the AuthZEN Access Request Member Names registry established by {{ARAP}}. + +| Name | Extension Point | Description | +|---|---|---| +| `actor` | `client` | Immediate actor on whose behalf the PEP submits the Access Request, with an optional nested `act` chain. | +| `source` | `client` | Audit-trail context describing where the request originated. | +| `session_id` | `client.source` | Identifier of a bounded interaction context that produced the request (chat or agent conversation, application session, CLI invocation, workflow thread). | +| `external_url` | `client.source` | URL of an external system that motivated the request. | +| `integration_id` | `client.source` | Identifier of an upstream integration or workflow that produced the request. | + +Change Controller for all entries: OpenID Foundation AuthZEN Working Group. Specification Document for all entries: This document. + +--- back + +# Examples + +## Submission by an Agent Runtime + +This non-normative submission is made by an agent runtime acting for a user. The runtime supplies `client.actor` so the Access Request Service can route approval to the agent's owner, and `client.source` to record the originating session. + +~~~ http +POST /access/v1/requests HTTP/1.1 +Host: pdp.example.com +Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA +Content-Type: application/json +Idempotency-Key: 9c1f5d12-2a18-4cba-8a5e-e0e8e2b6b5c7 + +{ + "subject": { + "type": "user", + "id": "alice@example.com" + }, + "resource": { + "type": "tool", + "id": "crm.search_accounts" + }, + "action": { + "name": "invoke" + }, + "context": { + "business_justification": "Assembling Q2 renewal report for customer ACME-1042" + }, + "requested_access": { + "requested_until": "2026-05-19T15:00:00Z" + }, + "client": { + "id": "renewal_assistant", + "actor": { + "id": "agent_renewal_assistant_v3", + "issuer": "https://agents.example.com", + "type": "ai_agent" + }, + "source": { + "session_id": "session_01HX69WJ8Q0K7P4F0V0K9D6Z7N" + } + }, + "denial": { + "evaluation_id": "eval_01HX6A9D2M7N0F4G3K2T9P1B8X", + "evaluated_at": "2026-05-12T15:00:00Z", + "expires_at": "2026-05-12T15:10:00Z", + "reason": "agent_authority_missing", + "binding_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBkcC0xIn0.eyJldmFsdWF0aW9uX2lkIjoiZXZhbF8wMUhYNkE5RDJNN04wRjRHM0syVDlQMUI4WCIsImNsYXNzIjoiY3JtX3Rvb2xzIn0.aGFzaA", + "template": "agent_tool_class_approval" + } +} +~~~ + +# Document History + +-00 + +* Extracted actor delegation, the `client.actor` and `client.source` members, and actor-chain verification from the AuthZEN Access Request and Approval Profile without changing their force or conditions. diff --git a/profiles/authzen-access-request-approval/authzen-access-request-approval-profile-1_0.md b/profiles/authzen-access-request-approval/authzen-access-request-approval-profile-1_0.md index 9c43dc9b..935ef3c4 100644 --- a/profiles/authzen-access-request-approval/authzen-access-request-approval-profile-1_0.md +++ b/profiles/authzen-access-request-approval/authzen-access-request-approval-profile-1_0.md @@ -30,6 +30,30 @@ author: email: public@karlmcguinness.com normative: + BULK: + title: "AuthZEN Bulk Access Requests Profile 1.0" + target: "https://openid.github.io/authzen/authzen-access-request-bulk-profile-1_0.html" + author: + - + ins: K. McGuinness + name: Karl McGuinness + date: 2026 + ACTOR: + title: "AuthZEN Actor Delegation Profile 1.0" + target: "https://openid.github.io/authzen/authzen-access-request-actor-profile-1_0.html" + author: + - + ins: K. McGuinness + name: Karl McGuinness + date: 2026 + CALLBACK: + title: "AuthZEN Callback Notifications Profile 1.0" + target: "https://openid.github.io/authzen/authzen-access-request-callback-profile-1_0.html" + author: + - + ins: K. McGuinness + name: Karl McGuinness + date: 2026 RFC9110: RFC9457: RFC3339: @@ -39,8 +63,6 @@ normative: RFC7516: RFC7517: RFC7519: - RFC6901: - RFC8693: RFC8785: I-D.bhutton-json-schema: I-D.bhutton-json-schema-validation: @@ -60,41 +82,95 @@ normative: name: Atul Tulshibagwale date: 2026-04-29 +informative: + OBLIGATIONS: + title: "AuthZEN Profile for Obligations 1.0" + target: "https://openid.github.io/authzen/authzen-obligations-profile-1_0.html" + author: + - + org: OpenID AuthZEN Working Group + date: 2026 + CATALOG: + title: "AuthZEN Access Request Catalog Profile 1.0" + target: "https://openid.github.io/authzen/authzen-access-request-catalog-profile-1_0.html" + author: + - + ins: K. McGuinness + name: Karl McGuinness + date: 2026 + --- abstract -This specification defines an extension profile for the OpenID AuthZEN Authorization API that allows a Policy Enforcement Point (PEP) to submit an access request when an authorization decision is denied but requestable. The profile preserves the AuthZEN Authorization API decision model: a denied decision remains a denial and MUST NOT be treated as access. It adds a requestable denial context, an access request endpoint, a task handle for the asynchronous workflow that resolves a denial, and a re-evaluation completion mode that lets the Policy Decision Point remain authoritative at enforcement time after approval. +This profile extends the OpenID AuthZEN Authorization API so a Policy Enforcement Point (PEP) can submit an access request after a requestable denial. The profile preserves the AuthZEN Authorization API decision model: a denied decision remains a denial and MUST NOT be treated as access. It defines denial context, an access request endpoint, a task handle for the asynchronous workflow, and re-evaluation after approval to keep the Policy Decision Point authoritative at enforcement time. --- middle # Introduction -The AuthZEN Authorization API enables a Policy Enforcement Point (PEP) to ask a Policy Decision Point (PDP) whether a Subject may perform an Action on a Resource within a Context. The PDP returns a Decision indicating whether the operation is allowed or denied. +The AuthZEN Authorization API lets a Policy Enforcement Point (PEP) ask a Policy Decision Point (PDP) whether a Subject may perform an Action on a Resource within a Context. The PDP returns an allow or deny Decision. + +Beyond a bare allow or deny, the AuthZEN family gives a PEP several ways to continue. The ones this profile relates to are: + +* An obligation ({{OBLIGATIONS}}) is an action the PEP itself performs to honor a permit the PDP has already made. No second evaluation follows. +* A partial-evaluation residual is policy the caller completes locally. No second evaluation follows. +* An Access Request resolves a denial with an input the PEP cannot produce on its own: an approval or a grant recorded out of band. The PDP evaluates again and remains authoritative. +* A transient denial is retried after a delay with nothing changed ({{reevaluation-denials}}). + +Each fails closed: an obligation the PEP cannot perform turns a permit into a deny ({{OBLIGATIONS}}), and a residual or an Access Request the PEP cannot use leaves the denial standing. How these mechanisms compose is defined by the profiles that carry them. + +This profile defines the Access Request. When authority is fixed at provisioning time through roles, scopes, or service-account grants, a runtime denial ends the interaction. When approval is possible during execution, a requestable denial lets the PEP submit an Access Request to an approval workflow, hold a Task Handle while the workflow runs, and re-evaluate access against current policy after approval ({{protocol-overview}}). The profile serves autonomous callers and user-facing applications alike. Companion profiles define bulk Access Requests ({{BULK}}), callback notifications ({{CALLBACK}}), actor delegation ({{ACTOR}}), and catalog-backed request input ({{CATALOG}}). + +An approval workflow modeled as an obligation under {{OBLIGATIONS}} is outside this profile: it carries no Task Handle, no denial binding, and no re-evaluation. Where an approver's decision must be recorded and evaluated again, it is an Access Request. Workflow engines, approval policy languages, ticketing systems, entitlement catalogs, user interfaces, and approver-facing inbox or enumeration APIs are also out of scope; the PDP or Access Request Service supplies them, including how human or automated evaluators discover and act on pending requests. -In classic deployments, the authority a caller needs is largely fixed at provisioning time. A user is granted a role; an OAuth client is registered with a set of scopes; a service account is granted access to a database. Runtime denials are uncommon and typically indicate misconfiguration or attack, and the appropriate response is to log, alert, or refuse. +The presence of `context.access_request` does not weaken the AuthZEN Authorization API decision. A PEP MUST NOT grant access based on a requestable denial. Access is permitted only after an approved completion result is enforced according to this profile. -Modern systems increasingly require authorization decisions to evolve during ongoing execution due to delegation, dynamic resource discovery, scope expansion, and long-running agent activity: +## Protocol Overview {#protocol-overview} -* An AI agent executing a multi-step task discovers, mid-execution, that it needs to read a document, query a record, or post to a channel that was not declared when the agent was deployed. Each previously unseen resource produces a denial, and the same agent may produce many such denials over the course of a single task. -* An OAuth Authorization Server issuing fine-grained access tokens cannot pre-enumerate the cross-resource scope combinations a fleet of long-lived clients will request over time, and so it denies token requests for scope combinations that require governance review (policy evaluation, risk scoring, or human approval) before issuance. -* A gateway acting as a PEP for an internal API encounters a user attempting an operation that requires elevated authority their standing role does not grant. The deployment expects the gateway to route the request to an owner for approval rather than refuse the call outright. -* A Security Token Service minting tokens for downstream calls discovers that a particular downstream resource requires per-call approval beyond what the upstream token already conveys. +~~~ ascii-art ++---------+ +---------+ +----------------+ +| PEP | | PDP | | Access Request | +| | | | | Service | ++----+----+ +----+----+ +-------+--------+ + | | | + | 1. Access Evaluation | | + |---------------------------------->| | + | | | + | 2. decision=false | | + | context.access_request | | + |<----------------------------------| | + | | | + | 3. Submit Access Request | | + |------------------------------------------------------------------->| + | | | + | 4. task handle | | + |<-------------------------------------------------------------------| + | | | + | 5. Poll task or receive callback | | + |------------------------------------------------------------------->| + |<-------------------------------------------------------------------| + | | | + | 6. Re-evaluate | | + |---------------------------------->| | + |<----------------------------------| | +~~~ -In each pattern, the denial is not a terminal error. It is a signal that further authority is required before the caller can proceed, that the deployment has a workflow capable of evaluating that request, and that the caller should hand off through a defined protocol surface rather than guess at remediation. Autonomous callers heighten this requirement: an agent, gateway, or token service has no browser to open and no human present at the moment of denial, and the volume of denials a single such caller produces makes per-deployment integrations unsustainable. +Steps 1 and 6 use the AuthZEN Access Evaluation API. Step 6 is a new evaluation, not a resumption of the denied operation. The PEP MUST NOT permit the requested operation based only on the presence of `context.access_request`. -The same need has long existed in user-facing patterns. SaaS applications surface approval prompts to end users when access is missing. Identity governance, ITSM, and case-management platforms accept access requests routed from enforcing applications. These flows are typically implemented through vendor-specific integrations because the protocol layer between authorization enforcement and the workflow that resolves a denial is not standardized. PEPs without such a standardized surface fall back to non-standard user-interface messages, out-of-band tickets, or vendor-specific governance integrations. +In step 5 the PEP can poll the task, receive a callback ({{CALLBACK}}), or otherwise use the Task Handle to determine completion. -This profile defines that protocol layer: a narrow, interoperable mechanism for requestable denials that applies uniformly to autonomous runtime callers and to user-facing approval flows. The flow is: +The flow is the same for a caller with no human present. An autonomous agent that receives a requestable denial for a newly discovered tool submits the Access Request, persists the Task Handle, continues independent work, and re-evaluates the tool invocation when approval completes. -1. The PEP evaluates access using the AuthZEN Access Evaluation API. -2. The PDP returns `decision: false` and a structured `access_request` object in the Decision Context when the denial can be resolved by a workflow capable of evaluating the request. -3. The PEP submits an access request to the Access Request Endpoint. -4. The Access Request Service returns an opaque task handle. -5. The PEP can poll the task, receive a callback, or otherwise use the task handle to determine completion. -6. When the task is approved, the PEP performs a new AuthZEN Authorization API evaluation; the PDP remains authoritative at enforcement time. +## Protocol Invariants {#protocol-invariants} -This specification intentionally does not define a workflow engine, approval policy language, ticketing system, entitlement catalog, user interface, or approver-facing inbox or enumeration API. Those capabilities are the responsibility of the PDP or Access Request Service. In particular, how an approver discovers and acts on pending requests is intentionally out of scope, so that approver-facing surface is not interoperable across implementations by design. The purpose of this profile is to standardize the handoff between authorization enforcement and the workflow that resolves a denial, so that any PEP, autonomous or user-facing, can route denials through a uniform interface to whatever evaluator the deployment uses, human or automated. +The rules of this profile rest on seven invariants, each stated normatively in the section that defines it: -This profile resolves missing authority, not missing information. A denied evaluation that could be completed with attributes or context the caller already holds is a matter of supplying those inputs; partial evaluation, where the PDP returns the residual conditions a caller can satisfy locally, addresses that case. A requestable denial is different: the authority does not yet exist at evaluation time, and is created by an asynchronous governance process, often a human approver. No information the PEP can supply would satisfy such a denial; a workflow must produce new authority first. The two mechanisms are orthogonal and can be used together. +1. A requestable denial is still a denial. +2. An Access Request does not itself grant access. +3. An approval does not itself grant access. +4. The PDP makes a new authorization decision at enforcement time. +5. An Access Request is bound to the denied evaluation it remediates. +6. An approval is bound to the Access Request it completed. +7. The PEP carries binding material between the roles but does not establish or vouch for the authority it represents. # Requirements Notation and Conventions @@ -102,21 +178,6 @@ This profile resolves missing authority, not missing information. A denied eval The terms Policy Decision Point (PDP), Policy Enforcement Point (PEP), Subject, Resource, Action, Context, and Decision are used as defined by {{AuthZEN}}. -# Design Goals - -This profile has the following design goals: - -* Preserve the AuthZEN Authorization API's allow/deny decision model. -* Preserve the AuthZEN Authorization API's stateless evaluation model: the PDP retains no decision state between the denial and the re-evaluation, and the durable request and approval state, including any denial-binding record, lives in the Access Request Service role, not the PDP ({{requestable-denial-context}} defines how that record is carried). -* Provide an interoperable interface for any PEP to route denied access to a centralized governance evaluator, whether that evaluator is human (an owner, approver, or delegate), automated (a policy engine, risk engine, or rule-based evaluator), or a combination of the two. -* Support high-volume autonomous callers by combining a uniform per-denial submission shape with Access Request Service workflow patterns that absorb load (broad-scope approvals, auto-approval, pre-approval, bulk approval). -* Make requestability explicit and machine-readable so autonomous PEPs can construct a conformant submission without human intervention at submission time. -* Provide an opaque task handle suitable for the asynchronous workflow that resolves a denial. -* Avoid embedding a workflow policy language in the authorization response. -* Allow approval and evaluation systems such as automated policy engines, risk engines, AI supervisors, ITSM platforms, identity governance platforms, chat approval, case management, or custom governance systems to sit behind a common endpoint. -* Support re-evaluation after approval so the PDP remains authoritative at enforcement time. -* Provide enough audit correlation to bind the original denial, submitted request, approver action, and final authorization result. - # Terminology Access Request: @@ -125,7 +186,8 @@ Access Request: Access Request Service: : A role that receives Access Request submissions and manages the resulting approval task. This role MAY be played by the PDP itself (logically part of the PDP), by a service trusted by the PDP (such as a governance platform), or by an independent service operating with delegated authority from the PDP. -: References to "the Access Request Service" in this profile refer to whichever entity plays this role for a given deployment. The protocol surface, authorization rules, and binding requirements apply uniformly regardless of which deployment shape is chosen. +Independent Access Request Service: +: For denial binding, an Access Request Service without trusted access to denied-evaluation state shared with or delegated by the PDP. Independence describes state access, not organizational ownership or absence of trust between the roles. Requestable Denial: : An AuthZEN Authorization API Decision with `decision` set to `false` and a Decision Context indicating that the denied access can be requested through an Access Request Endpoint. @@ -133,70 +195,48 @@ Requestable Denial: Task Handle: : An opaque identifier and associated status endpoint representing the lifecycle of an Access Request. +Autonomous PEP: +: A PEP that acts without a human present to confirm what it fetches or submits. + Approval Result: : The completed result of an Access Request task. An Approval Result does not itself permit access; the PEP uses it to obtain an AuthZEN Authorization API allow decision through a new Access Evaluation, or enforces it according to a profile-defined completion mode where one applies. -Authorization-Relevant Context: -: The subset of AuthZEN Authorization API `context` members that the PDP treats as authorization input and includes in denial binding and approval scope. Profile machinery members (`access_request`, `evaluation_id`, `evaluated_at`, and `reason`) are not authorization-relevant, and a PDP SHOULD also exclude volatile members (timestamps, nonces, or request identifiers such as `context.time`) so the set compares equal across the denial and a later submission or re-evaluation. - - Denial binding, approval-scope matching, and idempotent-submission comparison all compare this set, so when any member is authorization-relevant the PDP MUST make it explicit and integrity-protected, and the Access Request Service MUST use exactly that set: +Completion Mode: +: The mechanism by which an approved Access Request is completed to obtain an authorization decision or credential for enforcement. In this profile, the mode is identified by `result.mode`. The only base mode is `reevaluate`: the PEP performs a new Access Evaluation carrying `context.approval`, and the PDP remains authoritative. Profiles can define additional completion modes, such as token issuance. See {{completion-semantics}}. - - with a `binding_token`, the token carries the set as a `binding_context_members` claim ({{binding-token-integrity}}); - - with `evaluation_id`, the set is recorded for that evaluation in shared state and resolved server-side. - - Absent an integrity-protected or server-resolved set, the authorization-relevant Context is empty and only Subject, Resource, and Action bind. +Authorization-Relevant Context: +: The subset of AuthZEN Authorization API `context` members that the PDP treats as authorization input and includes in denial binding and approval scope. -# Protocol Overview + The rules that fix this set for a given evaluation are in {{structural-comparison}}. -~~~ ascii-art -+---------+ +---------+ +----------------+ -| PEP | | PDP | | Access Request | -| | | | | Service | -+----+----+ +----+----+ +-------+--------+ - | | | - | 1. Access Evaluation | | - |---------------------------------->| | - | | | - | 2. decision=false | | - | context.access_request | | - |<----------------------------------| | - | | | - | 3. Submit Access Request | | - |------------------------------------------------------------------->| - | | | - | 4. task handle | | - |<-------------------------------------------------------------------| - | | | - | 5. Poll task or receive callback | | - |------------------------------------------------------------------->| - |<-------------------------------------------------------------------| - | | | - | 6. Re-evaluate | | - |---------------------------------->| | - |<----------------------------------| | -~~~ +# Roles and Binding {#roles-trust-and-keys} -The access evaluation in step 1 uses the existing AuthZEN Access Evaluation API. The denial in step 2 is still a denial. The PEP MUST NOT permit the requested operation based only on the presence of `context.access_request`. Step 6 is a new AuthZEN Authorization API evaluation against the PDP so the PDP remains authoritative at enforcement time. +Three roles take part in this profile: -The flow supports execution continuity across the denial, approval, and re-evaluation boundary. The Task Handle returned in step 4 is portable: it survives PEP restart, replacement, or handoff to a different runtime instance, and it can be polled or completed by any caller that can authenticate as authorized for the bound Subject, Resource, Action, and operation (see {{task-status-endpoint}} and {{authorization-and-authentication}}). A caller that pauses execution at the denial in step 2 (for example, an agent runtime or a workflow orchestrator) can therefore persist the Task Handle, allow approval to proceed asynchronously over minutes, hours, or days, and resume execution at step 5 or step 6 from a fresh process without rebuilding session state. This profile does not define the orchestration that pauses and resumes execution; it provides the protocol primitives a caller needs to implement it. +* The PEP enforces decisions and carries the flow from denial to re-evaluation. +* The PDP decides and remains authoritative at enforcement time. +* The Access Request Service runs the approval workflow. -The Access Request Service MAY additionally publish lifecycle events for governance, audit, and analytics consumers through deployment-level event subscriptions defined by companion specifications. Such channels are independent of the per-task callback and are not used for enforcement. +The protocol, authorization, and binding requirements apply regardless of which entity plays the Access Request Service role. -# Discovery {#discovery} +## Binding Model {#binding-model} -A PDP supporting this profile MUST publish an `access_request_endpoint` in PDP metadata. The endpoint value MUST be an HTTPS URI. +Denial and approval binding material passes through the PEP for verification by the receiving role: -A PDP supporting this profile SHOULD include the following capability URN in the `capabilities` array: +| Exchange | Signed proof | Trusted-state lookup | +|---|---|---| +| Denial: PDP to Access Request Service | Self-contained `binding_token`, signed by the PDP | `evaluation_id`, resolved by the Access Request Service | +| Approval: Access Request Service to PDP | `approval.state`, signed by the service or the PDP acting through it | `approval.id`, resolved by the PDP | -`urn:openid:authzen:capability:access-request` +These are verification patterns, not mutually exclusive members. With a `binding_token`, an accompanying `evaluation_id` serves only correlation and audit. `approval.id` remains present alongside signed `approval.state`. The `state` member also permits other verifier state, not only signed proof ({{approval-result}}). -A PDP that issues or verifies signed values for use under this profile (for example, a JWS-signed `binding_token` or a JWS `approval.state`) MUST publish a `jwks_uri` in PDP metadata. The value is an HTTPS URI of a JWK Set {{RFC7517}} document containing the verification keys for the signed artifacts this profile defines: PDP-issued `binding_token` values and `approval.state` values signed by the PDP's Access Request Service. Keys are distinguished by their `kid` and by the JWS `iss`. +The PEP carries these values through the exchange without interpreting their protected contents ({{requestable-denial-context}}, {{approval-result}}). -Each JWK in the set SHOULD include a `kid` parameter so JWS signatures issued with a `kid` header can be resolved to the corresponding verification key, and SHOULD include a `use` parameter distinguishing signing keys (`use: "sig"`) from any other keys advertised. +## PDP Metadata {#discovery} -Verifiers cache the JWK Set per HTTP cache headers and refresh it on key-rotation events. An unrecognized `kid` SHOULD cause the verifier to refresh the JWK Set before rejecting the input. +PDP metadata carries the Access Request Endpoint as `access_request_endpoint` and the capability URN `urn:openid:authzen:capability:access-request`; the publication rules are in {{pdp-metadata-rules}}. -The following is a non-normative metadata example: +Non-normative metadata example: ~~~ json { @@ -204,59 +244,44 @@ The following is a non-normative metadata example: "access_evaluation_endpoint": "https://pdp.example.com/access/v1/evaluation", "access_evaluations_endpoint": "https://pdp.example.com/access/v1/evaluations", "access_request_endpoint": "https://pdp.example.com/access/v1/requests", - "jwks_uri": "https://pdp.example.com/access/v1/jwks", "capabilities": [ "urn:openid:authzen:capability:access-request" ] } ~~~ -The `access_request_endpoint` MAY be hosted by the PDP itself, by a service trusted by the PDP, or by an independent service operating with delegated authority from the PDP. When hosted by a different service, the PDP metadata MUST identify the endpoint actually used by the PEP to submit access requests. +# Requestable Denial -# Requestable Denial Context {#requestable-denial-context} +## Requestable Denial Context {#requestable-denial-context} When an AuthZEN Access Evaluation response denies access and the denial is eligible for an access request, the PDP MAY include an `access_request` object in the Decision Context. -The presence of `context.access_request` is the signal that the denial is requestable. The PDP MUST include this object only when the denied access is eligible for submission to an Access Request Endpoint; the PEP MUST treat the absence of this object as a non-requestable denial regardless of any other context members. +The presence of `context.access_request` is the signal that the denial is requestable. The `access_request` object has the following members: +`expires_at`: +: REQUIRED. String containing an {{RFC3339}} timestamp. Indicates when the requestable denial hint expires. The PEP echoes this value as `denial.expires_at` when submitting the Access Request ({{pep-construct}}). + `endpoint`: -: OPTIONAL. HTTPS URI. The endpoint to which the PEP submits the access request. If omitted, the PEP MUST use the `access_request_endpoint` from PDP metadata ({{discovery}}). +: OPTIONAL. HTTPS URI. The endpoint to which the PEP submits the access request. `template`: -: OPTIONAL. String. An opaque template identifier that can guide the Access Request Service. Implementations typically map `template` to a stable identifier of the approval workflow, request schema, governance policy, or categorical source code that applies to this denial. The value is not a policy language and MUST NOT be interpreted by the PEP except for display or request submission. - -`expires_at`: -: REQUIRED. String containing an {{RFC3339}} timestamp. Indicates when the requestable denial hint expires. The PEP echoes this value as `denial.expires_at` when submitting the Access Request. The Access Request Service MUST reject submissions received after this time, after applying any clock-skew tolerance it has configured (see {{impl-considerations}}). - -`binding_token`: -: OPTIONAL in same-service or shared-state deployments, and REQUIRED when the Access Request Service is independent of the PDP (see the denial-binding forms below). String. Opaque context to be returned to the Access Request Service when submitting the access request. The PEP MUST NOT decode, modify, or interpret this value. The PEP returns it unchanged as `denial.binding_token` when submitting the Access Request ({{access-request-submission}}). When present, the value MUST be integrity protected in a way the Access Request Service can verify, and SHOULD be a JSON Web Signature (JWS) {{RFC7515}} in compact serialization, signed by the PDP, with a payload (such as a JWT {{RFC7519}}) that the Access Request Service can verify and bind to the original denied evaluation. JSON Web Encryption (JWE) {{RFC7516}} MAY be used in addition to integrity protection when the payload contains information that must not be visible to the PEP, for example by encrypting a signed payload. +: OPTIONAL. String. An opaque template identifier that can guide the Access Request Service. `display`: -: OPTIONAL. Object. Localizable user-interface hints such as title, description, or recommended call-to-action text. The PEP MAY ignore this member. - -`form_url`: -: OPTIONAL. HTTPS URI. URL of a form, hosted by the Access Request Service or another service trusted by the deployment, where the requester can supply additional information required for the Access Request. Suitable for PEPs that render the form for a human user. See {{machine-readable-forms}}. - -`request_schema_url`: -: OPTIONAL. HTTPS URI. URL where the Access Request Service publishes a machine-readable description of the augmentations the PEP must add to the submission's `context` and `requested_access` objects. RECOMMENDED to be a JSON Schema {{I-D.bhutton-json-schema}} {{I-D.bhutton-json-schema-validation}} document. Suitable for autonomous PEPs and for PEPs that render forms natively against a schema. See {{machine-readable-forms}}. - -`request_catalogs_url`: -: OPTIONAL. HTTPS URI. URL of a Catalogs Document describing how the PEP resolves form fields whose values are selected from a backing catalog. See {{catalog-references}}. +: OPTIONAL. Object. Localizable user-interface hints such as title, description, or recommended call-to-action text. -The Decision's reason (why the evaluation returned `false`) is conveyed in the Decision Context. The AuthZEN Authorization API treats the contents of the Decision Context as implementation-defined; this profile uses `context.reason` as a machine-readable reason code, which the PEP echoes as `denial.reason` when submitting an Access Request. - -The PDP MUST provide enough denial-binding material for the Access Request Service to verify that a submitted Access Request corresponds to the denied evaluation and is still fresh. A requestable denial MUST include `expires_at` and at least one of two denial-binding forms: +`binding_token`: +: OPTIONAL in same-service or shared-state deployments ({{shared-state-deployments}}), and REQUIRED when the Access Request Service is independent of the PDP. String. Opaque context to be returned to the Access Request Service when submitting the access request. -- **`binding_token` (by value).** An integrity-protected token that protects or constrains the requestable-denial expiry. The PDP signs it and retains no state. This form works in any topology, and is REQUIRED when the Access Request Service does not share state with the PDP (an independent Access Request Service). For that independent topology the `binding_token` MUST be self-contained: it carries the denied Subject, Resource, Action, and authorization-relevant Context by value (inline or as a `binding_hash`) and the Access Request Service verifies it offline against the PDP's published key, per {{binding-token-integrity}}. A `binding_token` that only references shared state, for example one carrying `evaluation_id` alone, does not satisfy the independent-Access-Request-Service requirement. -- **`evaluation_id` (by reference).** A stable identifier ({{evaluation-identifier}}) that the Access Request Service resolves against state shared with, or delegated by, the PDP, within a server-side binding window. This form applies only to same-service or shared-state deployments, because a stateless PDP retains no decision state for an independent service to fetch. + Integrity protection and encryption are defined in {{denial-binding}}; the PEP's handling is in {{pep-construct}}. -A PDP MAY emit both forms. When a `binding_token` is present it is the authoritative denial binding, and any accompanying `evaluation_id` serves only as a correlation and audit identifier rather than a second binding form; the Access Request Service resolves `evaluation_id` as a binding form only when no `binding_token` is present. This is why a PDP MAY follow the conformance recommendation to return a stable `context.evaluation_id` ({{evaluation-identifier}}) even when it also emits a `binding_token`. +Two further members of this object, `form_url` and `request_schema_url`, are defined in {{machine-readable-forms}}. -The durable denial-binding record lives in the Access Request Service role, not the PDP. This is the denial-side mirror of the re-evaluation rule that requires `approval.state` when the PDP cannot resolve `approval.id` from shared or delegated state ({{completion-semantics}}). When neither binding form is available, or when the Access Request Service cannot determine that the binding is unexpired, the PDP MUST NOT include `context.access_request` in the Decision Context. +AuthZEN leaves Decision Context implementation-defined. This profile uses `context.reason` for the machine-readable denial reason, which the PEP echoes as `denial.reason` in the Access Request. -The following is a non-normative example: +Non-normative example: ~~~ json { @@ -269,10 +294,6 @@ The following is a non-normative example: "endpoint": "https://pdp.example.com/access/v1/requests", "template": "manager_approval", "expires_at": "2026-04-30T20:25:00Z", - "binding_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBkcC0xIn0.eyJldmFsdWF0aW9uX2lkIjoiZXZhbF8wMUhYNFkyUDhCUTRZM0YwVjBLOUQ2WjdNMSJ9.bXBfc2lnbmF0dXJl", - "form_url": "https://requests.example.com/forms/manager_approval", - "request_schema_url": "https://requests.example.com/schemas/manager_approval.json", - "request_catalogs_url": "https://requests.example.com/catalogs/manager_approval.json", "display": { "title": "Request access", "description": "Manager approval is required before this document can be opened." @@ -284,1141 +305,1119 @@ The following is a non-normative example: ## Evaluation Identifier {#evaluation-identifier} -This profile defines `evaluation_id` as a first-class identifier for an AuthZEN Authorization API evaluation, used by the Access Request Service to bind a submitted Access Request to the denied evaluation it remediates ({{access-request-submission}}). +`evaluation_id` identifies an AuthZEN Authorization API evaluation for denial binding and audit correlation. -`evaluation_id` is also the audit thread that links a later re-evaluation back to the initial denied attempt that prompted the Access Request. Re-evaluation is keyed by the `approval` object rather than by `evaluation_id` ({{completion-semantics}}); nonetheless, the Access Request Service SHOULD retain the original `evaluation_id` in the approval record so the full sequence (denied evaluation, Access Request, approval, and re-evaluation) can be reconstructed for audit. +Re-evaluation uses the `approval` object, not `evaluation_id` ({{completion-semantics}}). A PDP returns `evaluation_id` as a member of the AuthZEN Decision Context: `context.evaluation_id`, a string. The PEP echoes the captured identifier as `denial.evaluation_id` when submitting an Access Request. -`evaluation_id` MUST be stable for a given evaluation: subsequent retrievals or echoes of the same evaluation MUST return the same identifier. PDPs SHOULD generate identifiers that are unique within the PDP's namespace (for example, ULIDs or UUIDs). An identifier MAY be reused across distinct evaluations only after the original evaluation's binding window has expired. The binding window is the period during which the Access Request Service can resolve or validate the identifier for Access Request submission; it MUST NOT extend beyond `context.access_request.expires_at`. - -A PDP that returns `context.access_request` without an integrity-protected `binding_token` MUST include `evaluation_id` so the Access Request Service has verifiable denial-binding material; this is the by-reference form and applies only to shared-state deployments ({{requestable-denial-context}}). - Profiles that bridge to specifications using a transaction-binding identifier (for example, a token-issuance profile whose underlying specification carries a separate transaction identifier claim) MAY use `evaluation_id` directly as that identifier when its uniqueness, stability, and binding-window properties match the consuming specification's requirements. A PDP MAY return `evaluated_at` as a member of the AuthZEN Decision Context: `context.evaluated_at`, an {{RFC3339}} timestamp indicating when the Decision was produced. The PEP echoes the captured timestamp as `denial.evaluated_at` when submitting an Access Request. -# Machine-Readable Forms {#machine-readable-forms} - -The OPTIONAL `form_url` and `request_schema_url` members of the `access_request` object ({{requestable-denial-context}}) describe additional submission fields the Access Request Service expects beyond those produced by the original AuthZEN Authorization API evaluation. PEPs interacting with deployments that do not include either member MAY omit form-schema processing entirely. - -* `form_url` identifies a form hosted by the Access Request Service or a service it trusts, suitable for PEPs that render the form for a human user. -* `request_schema_url` identifies a machine-readable description of the same augmentations, suitable for autonomous PEPs and for PEPs that render forms natively against a schema. - -When a deployment expects autonomous PEP submissions, the requestable denial SHOULD include `request_schema_url` referencing a JSON Schema {{I-D.bhutton-json-schema}} {{I-D.bhutton-json-schema-validation}} document that describes the augmentations the PEP MUST add to the submission's `context` and `requested_access` objects. An autonomous PEP MAY consume the schema directly to construct a valid submission. If the schema requires information the PEP cannot obtain or is not authorized to supply, the PEP MUST NOT fabricate values or submit an incomplete request; it MUST either surface the request for additional input, hand it to another authorized component, or treat the denial as not requestable by that PEP. - -Many existing IGA, ITSM, and approval platforms already use proprietary form description languages. Implementations built on top of such platforms MAY publish a JSON Schema document derived from their native form description. Some loss of fidelity is expected when translating between form description languages; the JSON Schema referenced by `request_schema_url` SHOULD provide enough information for an autonomous PEP to construct a conformant submission, while richer rendering, widget, and interaction details remain in `form_url`. +# Submitting the Access Request -Field values that are selected from a backing catalog (for example, applications, entitlements, roles, or cost centers) are described in a separate Catalogs Document referenced by `request_catalogs_url`. This profile keeps catalog references outside the form schema so the schema remains a pure description of data shape. See {{catalog-references}}. +The Access Request Endpoint accepts a submission and returns a Task Handle. Its deployment-specific URL comes from `access_request_endpoint` in PDP metadata or `context.access_request.endpoint` in the denial. -This profile does not define a UI rendering vocabulary. Deployments that need richer rendering hints (such as widget selection, layout, or conditional display) MAY layer a UI vocabulary, identified out of band, typically keyed by `template`. - -This profile does not define an agent protocol surface. Deployments serving agentic PEPs MAY additionally expose Access Request submission through an agent protocol where the tool input schema corresponds to the JSON Schema referenced by `request_schema_url`. Discovery of such surfaces is out of scope for this specification. - -# Catalog References {#catalog-references} - -The OPTIONAL `request_catalogs_url` member of the `access_request` object ({{requestable-denial-context}}) is the URL of a Catalogs Document that tells PEPs how to resolve form fields whose values come from backing catalogs (for example, applications, entitlements, roles, or cost centers). PEPs interacting with deployments that do not include `request_catalogs_url` MAY omit Catalog Endpoint resolution. - -The Catalogs Document is a sibling artifact to the form schema; it does not modify or extend the JSON Schema referenced by `request_schema_url`. This feature is typically paired with the form-schema feature ({{machine-readable-forms}}). - -## Catalogs Document - -The Catalogs Document is a JSON object retrieved from `request_catalogs_url` using HTTP `GET`. It has the following members: +## Access Request Submission {#access-request-submission} -`fields`: -: REQUIRED. Object. Each member name is a JSON Pointer ({{RFC6901}}) into the form data instance described by the form schema, identifying a field whose value is selected from a catalog. Each member value is a Catalog Reference object. +The PEP submits an Access Request using the HTTP `POST` method as defined in {{RFC9110}}. -Implementations MAY include additional members for documentation or vendor metadata; consumers MUST ignore members they do not recognize. +### Request Body {#submission-request-body} -A Catalog Reference object has the following members: +{: #submission-additional-information} -`endpoint`: -: REQUIRED. HTTPS URI. Catalog Endpoint from which catalog items are retrieved. +For a single-item submission (`items` absent), the request body is a JSON object with the following members. {{BULK}} defines the changes for bulk submissions. -`search_param`: -: OPTIONAL. String. Query parameter used to pass a free-text search term to the Catalog Endpoint. Defaults to `q`. +`subject`: +: REQUIRED. The AuthZEN Subject from the denied evaluation. -`scope_params`: -: OPTIONAL. Object. Each member name is the query parameter sent to the Catalog Endpoint and the value is a JSON Pointer ({{RFC6901}}) into the form data instance identifying the source field. The PEP MUST resolve each pointer at request time and MUST NOT call the Catalog Endpoint until every referenced source field has a value. +`resource`: +: REQUIRED. The AuthZEN Resource from the denied evaluation. -`value_path`: -: OPTIONAL. String. JSON Pointer ({{RFC6901}}) into a Catalog Item, identifying the value the PEP places into the form field. Defaults to `/value`. +`action`: +: REQUIRED. The AuthZEN Action from the denied evaluation. -`label_path`: -: OPTIONAL. String. JSON Pointer ({{RFC6901}}) into a Catalog Item, identifying a human-readable label. Defaults to `/label`. +`denial`: +: REQUIRED. Object binding the Access Request to the denied AuthZEN Decision. It binds the submitted Subject, Resource, Action, and authorization-relevant Context. Its members are defined in {{submission-denial-object}}. -Non-normative example: +`context`: +: OPTIONAL. The AuthZEN Context from the denied evaluation, augmented with submission-time fields such as business justification. -~~~ json -{ - "fields": { - "/application_id": { - "endpoint": "https://requests.example.com/catalog/applications", - "search_param": "q" - }, - "/entitlement_id": { - "endpoint": "https://requests.example.com/catalog/entitlements", - "search_param": "q", - "scope_params": { "application_id": "/application_id" } - } - } -} -~~~ + This is Context from the original Access Evaluation request, not the PDP's Decision Context. Optional presence does not waive the preservation rule in {{pep-construct}}. -## Catalog Endpoint +`requested_access`: +: OPTIONAL. Object containing request-specific information such as requested duration, requested role, requested entitlement, or requested scope. This object does not define policy semantics and is interpreted by the Access Request Service. The following well-known optional members are defined; additional members MAY be included subject to {{extension-naming}}: -A Catalog Endpoint accepts an HTTP `GET` request and returns a paginated list of Catalog Items. + * `requested_until`: String. {{RFC3339}} timestamp requesting access through a specific absolute time. + * `emergency`: Boolean. When `true`, requests an expedited or emergency-access path subject to additional auditing. -The Catalog Endpoint MUST accept the following query parameters: +`client`: +: OPTIONAL. Object identifying the PEP or calling application submitting the Access Request, supplementing the authenticated caller identity. The following members are defined; implementations MAY include additional members. -* The search parameter named by `search_param` (default `q`): String. Free-text query supplied by the caller. -* The scope parameters named by `scope_params`: String values taken from other form data fields. -* `cursor`: OPTIONAL. String. Opaque pagination cursor returned by a previous response. -* `limit`: OPTIONAL. Integer. Caller-requested page size. The Catalog Endpoint MAY clamp or ignore this value. + * `id`: OPTIONAL. String. Stable identifier for the calling application or PEP deployment. + * `name`: OPTIONAL. String. Human-readable name of the calling application. -The Catalog Endpoint MAY accept additional deployment-specific parameters; receivers MUST ignore parameters they do not recognize. + The `actor` and `source` members of this object are defined by {{ACTOR}}. -A Catalog Endpoint SHOULD share an origin with the Access Request Endpoint and SHOULD accept the same caller credentials. Deployments that host catalogs on a different origin MUST establish a documented mechanism for obtaining credentials accepted by the Catalog Endpoint, for example through OAuth 2.0 Token Exchange {{RFC8693}}; this profile does not define cross-origin credential acquisition. +The body can also carry `callback` ({{CALLBACK}}). -A Catalog Endpoint MUST: +### The `denial` Object {#submission-denial-object} -* authenticate the caller; -* authorize the caller to enumerate the catalog; and -* return only items the caller is permitted to see for the original Subject, Resource, and Action. +The `denial` object echoes selected members of the PDP's denied evaluation response; the PEP's echo rule is in {{pep-construct}}. -The catalog response is itself an authorization boundary; it MUST NOT disclose entries the requester would not be permitted to request. +`expires_at`: +: REQUIRED. {{RFC3339}} timestamp indicating when the requestable denial hint expires, echoed unchanged. -## Catalog Response +`evaluation_id`: +: REQUIRED when `denial.binding_token` is absent; otherwise RECOMMENDED. A stable identifier for the denied evaluation, captured by the PEP and echoed unchanged ({{evaluation-identifier}}). -A successful response returns HTTP `200 OK` and a JSON object with the following members: +`binding_token`: +: REQUIRED when `denial.evaluation_id` is absent; otherwise OPTIONAL. String. Integrity-protected binding material echoed unchanged ({{requestable-denial-context}}). -`items`: -: REQUIRED. Array of Catalog Items. Each Catalog Item is a JSON object containing the value identified by `value_path` and SHOULD include the value identified by `label_path`. Items SHOULD include the following well-known optional members when applicable, and MAY include additional vendor-specific metadata: +`evaluated_at`: +: OPTIONAL. {{RFC3339}} timestamp indicating when the denial was produced. - * `description`: String. Human-readable description of the item. - * `risk_level`: String. Risk classification used by the deployment (for example, `low`, `medium`, `high`). Useful for agent and human triage. - * `granted`: Boolean. When `true`, indicates that the requester already has access to the item. Allows a PEP to suppress redundant or no-op Access Request submissions. - * `owner`: Object or String. Identifier or reference for the item's owner, when the catalog tracks ownership. +`reason`: +: OPTIONAL. String. Machine-readable reason code for the denial, echoed unchanged. -`next_cursor`: -: OPTIONAL. String. Opaque cursor that the caller passes as `cursor` to retrieve the next page. Absent when no further pages are available. +`template`: +: OPTIONAL. String. Echoed unchanged when the PDP provided one. The Access Request Service uses this value to route the request to the appropriate workflow. -`total`: -: OPTIONAL. Integer. Approximate total number of items matching the search and scope filters. Used as a hint only; the PEP MUST NOT rely on its accuracy. +### Request and Response Example {#submission-example} -Non-normative example: +This standalone, non-normative exchange assumes the original Access Evaluation request contained `context: {"project": "customer-renewal"}` and the PDP recorded `project` as authorization-relevant. The PEP preserves that input in the submission below. The denial specifies neither a template nor schema-required input; the Access Request Service resolves `evaluation_id` against trusted state. ~~~ http -GET /catalog/entitlements?application_id=app_123&q=customer&limit=2 HTTP/1.1 -Host: requests.example.com +POST /access/v1/requests HTTP/1.1 +Host: pdp.example.com Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA -Accept: application/json +Content-Type: application/json +Idempotency-Key: 7b8d0f0d-65a1-4af1-9fd3-a684f08a5d13 + +{ + "subject": {"type": "user", "id": "alice@example.com"}, + "resource": {"type": "document", "id": "q4-plan"}, + "action": {"name": "can_read"}, + "context": {"project": "customer-renewal"}, + "denial": { + "expires_at": "2026-04-30T20:25:00Z", + "evaluation_id": "eval_01HX4Y2P8BQ4Y3F0V0K9D6Z7M1" + } +} ~~~ ~~~ http -HTTP/1.1 200 OK +HTTP/1.1 202 Accepted Content-Type: application/json { - "items": [ - { - "value": "ent_abc", - "label": "Customer Records (Read)", - "description": "Read access to customer master data" - }, - { - "value": "ent_def", - "label": "Customer Records (Write)", - "description": "Write access to customer master data", - "risk_level": "high" - } - ], - "next_cursor": "eyJvZmZzZXQiOjJ9" + "task": { + "id": "arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4", + "status": "pending", + "status_endpoint": "https://pdp.example.com/access/v1/requests/arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4" + } } ~~~ -## PEP Resolution Rules +## Access Request Response {#access-request-response} -A PEP submitting an Access Request based on a form schema with a companion Catalogs Document: +A successful Access Request submission returns HTTP status code `201 Created` or `202 Accepted` and a JSON object containing a `task` member. The `task.status_endpoint` member is authoritative for subsequent status retrieval. A response MAY also include an HTTP `Location` header equal to `task.status_endpoint`. The HTTP status code does not determine the task's state; the PEP reads `task.status`, which may already be terminal when the service completes the request synchronously ({{ars-task}}). -* MUST treat field values resolved from a catalog as opaque identifiers; the value submitted is exactly the value identified by `value_path` in the chosen Catalog Item. -* MUST resolve every `scope_params` source field before calling the Catalog Endpoint for a dependent field. -* MUST NOT submit catalog values that were not returned by the Catalog Endpoint with the same scope parameters. -* SHOULD use `search_param` rather than enumerating large catalogs. -* MUST treat unknown members of a Catalog Item as informational and MUST NOT rely on them for enforcement. -* MUST NOT treat `granted` or any other Catalog Item member as an authorization decision. Such members MAY be used to suppress or shape Access Request submission, but MUST NOT be used as authorization input. +The response object has the following top-level members: -## Access Request Service Catalog Validation +`task`: +: REQUIRED. Task Handle returned for the submitted Access Request ({{task-handle-object}}). -The Access Request Service MUST validate submitted catalog identifiers at submission time. It MUST reject, normalize, or route for additional review any submitted catalog value that is no longer valid, no longer requestable by the caller, disabled, retired, or materially different in risk or ownership from the item resolved by the PEP. +`result`: +: OPTIONAL except where required by {{ars-task}}. Completion result for the task. -## Agent Protocol Catalogs {#catalog-agent-protocol} +A synchronous-completion example is provided in {{synchronous-submission-example}}. -Deployments serving agentic PEPs MAY additionally expose catalogs through an agent protocol. When such a protocol is used, each catalog SHOULD be exposed as a resource whose identifier or URI template encodes the same scope parameters described by `scope_params` (for example, `entitlements://{application_id}`). Resource read responses SHOULD use the Catalog Response shape defined in this section. +# Checking the Task -This profile does not define agent-protocol discovery or transport. When both an HTTP Catalog Endpoint and an agent-protocol catalog are exposed, they MUST return the same Catalog Items for equivalent scope parameters. +## Task Handle {#task-handle-object} -# Access Request Endpoint +The `task` object has the following members: -The Access Request Endpoint accepts an Access Request submission and returns a Task Handle. +`id`: +: REQUIRED. Stable, opaque, and unguessable task identifier. -The endpoint is identified by the `access_request_endpoint` PDP metadata parameter or by the `context.access_request.endpoint` value returned in a requestable denial. The endpoint path is deployment-specific. +`status`: +: REQUIRED. Current task status. Values are defined in {{task-status}}. -## Access Request Submission {#access-request-submission} +`status_endpoint`: +: REQUIRED. HTTPS URI used to retrieve task status. -The PEP submits an Access Request using the HTTP `POST` method as defined in {{RFC9110}}. +`expires_at`: +: OPTIONAL. {{RFC3339}} timestamp after which the task handle is no longer valid. -The request body is a JSON object with the following members: +`display`: +: OPTIONAL. Object containing user-interface hints for the pending request. -`subject`: -: REQUIRED. The AuthZEN Subject from the denied evaluation. +`progress`: +: OPTIONAL. Object describing approval workflow progress for tasks with multi-step approvals. The following members are defined: -`resource`: -: REQUIRED when `items` is absent; MUST be omitted when `items` is present. The AuthZEN Resource from the denied evaluation. + * `current_step`: OPTIONAL. Integer. One-based index of the step currently in progress. + * `total_steps`: OPTIONAL. Integer. Total number of approval steps configured for the task. + * `step_name`: OPTIONAL. String. Short identifier of the current step (for example, `manager_approval` or `resource_owner_review`). + * `awaiting`: OPTIONAL. Array. Identifiers of approvers whose action is currently expected. -`action`: -: REQUIRED when `items` is absent; MUST be omitted when `items` is present. The AuthZEN Action from the denied evaluation. +`links`: +: OPTIONAL. Object containing related URLs. Each member name is a link relation type and the value is an HTTPS URI. The following relation types are defined; implementations MAY define additional relation types. -`items`: -: OPTIONAL. Array. Multiple `(resource, action)` items submitted as a single bundled Access Request. When present, `resource` and `action` MUST be omitted at the top level. Each item is an object with the following members: + * `ticket`: URL where the requester (Subject) can view the request and its status. + * `review`: URL where an approver or administrator can review or act on the request. + * `cancel`: URL where the PEP can cancel the request, when PEP-initiated cancellation is supported. - * `resource`: REQUIRED. The AuthZEN Resource for this item. - * `action`: REQUIRED. The AuthZEN Action for this item. - * `requested_access`: OPTIONAL. Per-item `requested_access` overrides; merged with the top-level `requested_access` with item values taking precedence. - * `denial`: OPTIONAL. Per-item denial binding when items came from separate AuthZEN Authorization API evaluations. A per-item `denial` uses the same members as the top-level `denial` object. See the top-level `denial` definition below for coverage rules. +The `items` member of this object, present for bulk submissions, is defined in {{BULK}}. -`context`: -: OPTIONAL. The AuthZEN Context from the denied evaluation, augmented with submission-time fields such as business justification. Submission-time augmentations MUST NOT change or remove authorization-relevant context from the denied evaluation. When the Access Request Service needs to distinguish original evaluation context from submission-time input, deployments SHOULD place the latter in well-defined extension members rather than overwriting original context members. +## Task Status and Transitions {#task-status} -`denial`: -: Object binding the Access Request to the denied AuthZEN Decision. REQUIRED in the following cases: +The following task status values are defined: - * `items` is absent: the `denial` binds the single submitted Subject, Resource, Action, and authorization-relevant Context. - * `items` is present and any item lacks a per-item `denial`: the top-level `denial` is a bundle denial whose verifiable binding material MUST cover the Subject, authorization-relevant Context, and every Resource and Action in `items`. +`pending`: +: The request has been accepted and is awaiting processing or approval. - OPTIONAL when `items` is present and every item carries its own per-item `denial`. +`approved`: +: The request was approved. Approval does not by itself grant access; the PEP obtains access only through the completion mode in {{completion-semantics}}. - An Access Request whose denial binding does not cover the submitted Subject, Resource, Action, and authorization-relevant Context (for every item when `items` is present) MUST be rejected with `urn:openid:authzen:access-request:error:invalid_denial_binding`. +`denied`: +: The request was denied by the approval workflow. -`requested_access`: -: OPTIONAL. Object containing request-specific information such as requested duration, requested role, requested entitlement, or requested scope. This object does not define policy semantics and is interpreted by the Access Request Service. The following well-known optional members are defined; additional members MAY be included subject to {{extension-naming}}: +`expired`: +: The request expired before completion. - * `requested_until`: String. {{RFC3339}} timestamp requesting access through a specific absolute time. - * `emergency`: Boolean. When `true`, requests an expedited or emergency-access path subject to additional auditing. +`cancelled`: +: The request was cancelled by the requester, approver, administrator, or system. -`callback`: -: OPTIONAL. Object describing a callback endpoint where the Access Request Service can send completion notifications. +`failed`: +: The request could not be completed due to an error. -`client`: -: OPTIONAL. Object identifying the PEP or calling application submitting the Access Request, supplementing the authenticated caller identity. The following members are defined; implementations MAY include additional members. +The `partial` status for bulk tasks is defined by {{BULK}}. - * `id`: OPTIONAL. String. Stable identifier for the calling application or PEP deployment. - * `name`: OPTIONAL. String. Human-readable name of the calling application. - * `actor`: OPTIONAL. Object identifying the immediate actor on whose behalf the PEP submits the Access Request, when that actor differs from the Subject or when the deployment needs to audit the actor separately. The following members are defined; implementations MAY include additional members. - * `id`: REQUIRED. String. Stable identifier for the actor. - * `issuer`: OPTIONAL. String. Issuer, authority, tenant, or identity provider for the actor identifier. - * `type`: OPTIONAL. String. Actor category, such as `user`, `service`, `workload`, or `ai_agent`. - * `act`: OPTIONAL. Object. Nested actor representing the next link in a delegation chain, following the conventions in {{?I-D.mcguinness-oauth-actor-profile}}. Each `act` carries `sub` and `iss` (corresponding to `id` and `issuer` in the immediate actor) and optionally `sub_profile`; nesting represents the chain from the immediate actor outward toward the Subject. See {{delegation}}. - * `source`: OPTIONAL. Object. Audit-trail context describing where the request originated. The following members are defined; implementations MAY include additional members. - * `session_id`: OPTIONAL. String. Identifier of a bounded interaction context that produced the request, such as a chat or agent conversation, a web or mobile application session, a CLI invocation, or a long-running workflow thread. This is an audit-origin identifier and is distinct from any authentication or authorization session associated with the caller. - * `external_url`: OPTIONAL. HTTPS URI. URL of an external system (ticket, document, dashboard, chat thread) that motivated the request. - * `integration_id`: OPTIONAL. String. Identifier of an upstream integration or workflow that produced the request. +Implementations MAY define additional status values. - The `actor` and `source` objects are supplied for authorization, routing, and audit correlation. The Access Request Service MUST NOT rely on `client.actor` or `client.source` as authorization input unless the values are independently verified by the service. +### State Transitions {#state-transitions} -The `denial` object has the following members. Each field maps directly to a single member of the PDP's denied evaluation response; the `denial` object does not echo the full AuthZEN Decision because the binding material (`evaluation_id` and `binding_token`) provides stronger evidence of the denial than a verbatim JSON echo could. +In the base state machine, a task is either returned in a terminal state when the Access Request Service completes it synchronously, or returned in the `pending` state and subsequently transitions exactly once to one of the terminal states defined in {{task-status}}. Terminal states do not transition further. -`evaluation_id`: -: REQUIRED when `denial.binding_token` is absent; otherwise RECOMMENDED. A stable identifier for the denied evaluation, captured by the PEP from `context.evaluation_id` in the AuthZEN Decision and echoed unchanged here ({{evaluation-identifier}}). The Access Request Service MUST be able to resolve or validate `evaluation_id` before relying on it as denial-binding material. `evaluation_id` provides the strongest audit binding between the original denial and the submitted Access Request and SHOULD be preferred over `evaluated_at` alone. +The following transitions are defined from `pending`: -`evaluated_at`: -: OPTIONAL. {{RFC3339}} timestamp indicating when the denial was produced, echoed from `context.evaluated_at` of the denied evaluation. +| To | Trigger | +|---|---| +| `approved` | Approval workflow completes successfully. | +| `denied` | Approval workflow rejects the request. | +| `expired` | `task.expires_at` is reached before the request reaches a terminal state. | +| `cancelled` | The request is cancelled by the requester, approver, administrator, or PEP using the cancellation endpoint ({{cancellation}}). | +| `failed` | A system error prevents the request from completing. | -`expires_at`: -: REQUIRED. {{RFC3339}} timestamp indicating when the requestable denial hint expires, echoed unchanged from `context.access_request.expires_at` of the denied evaluation. The Access Request Service MUST reject submissions received after this time, after applying any clock-skew tolerance it has configured (see {{impl-considerations}}). +The `partial` status, which applies only to bulk tasks, is described in {{BULK}}. -`reason`: -: OPTIONAL. String. Machine-readable reason code for the denial, echoed unchanged from `context.reason` of the denied evaluation. +## Task Status Endpoint {#task-status-endpoint} -`binding_token`: -: REQUIRED when `denial.evaluation_id` is absent; otherwise OPTIONAL. String. Integrity-protected binding material echoed unchanged from `context.access_request.binding_token` of the denied evaluation ({{requestable-denial-context}}). The PEP MUST NOT decode, modify, or interpret this value; it returns the original PDP-issued value byte-for-byte. +The PEP retrieves task status with an HTTP `GET` ({{RFC9110}}) to `status_endpoint`. -`template`: -: OPTIONAL. String. Echoed unchanged from `context.access_request.template` of the denied evaluation, when the PDP provided one. The Access Request Service uses this value to route the request to the appropriate workflow. +Non-normative example: -The PEP determines the additional members of the `context` and `requested_access` objects from the JSON Schema referenced by the requestable denial's `request_schema_url`, when present. Field values that are selected from a backing catalog are resolved according to the Catalogs Document referenced by `request_catalogs_url`; see {{catalog-references}}. +~~~ http +GET /access/v1/requests/arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4 HTTP/1.1 +Host: pdp.example.com +Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA +Accept: application/json +~~~ -A PEP MUST submit an Access Request only for an AuthZEN Decision with `decision` equal to `false` and a `context.access_request` object present in the Decision Context. +A successful response returns a JSON object containing a `task` member. Completed task responses include a `result` member according to the rules in {{ars-task}}. -The submitted `denial` object for each requested item MUST include either `denial.binding_token` or `denial.evaluation_id`. The Access Request Service MUST reject a submission that lacks verifiable denial-binding material with `urn:openid:authzen:access-request:error:invalid_denial_binding`. +The PEP's polling rules are in {{pep-poll}}. -A PEP SHOULD include an `Idempotency-Key` header, following the conventions described in {{I-D.ietf-httpapi-idempotency-key-header}}. The Idempotency-Key covers the entire submission body, including all members of the `items` array when present. +Completion notification is defined by {{CALLBACK}}. -The Access Request Service SHOULD treat a repeated submission with the same `Idempotency-Key`, the same authenticated requester, and an equivalent submission body as the same request, returning the same Task Handle while the original request remains available. A submission with the same `Idempotency-Key` and authenticated requester but a materially different submission body MUST be rejected with `urn:openid:authzen:access-request:error:duplicate_request`. Two submission bodies are equivalent when they are identical under a deterministic comparison chosen by the Access Request Service (for example, a stable JSON canonicalization that ignores insignificant whitespace and object-member ordering, excluding the `Idempotency-Key` header itself). Because the same Access Request Service that recorded the `Idempotency-Key` evaluates the retry, this comparison is a single-server concern and need not be interoperable across implementations; a body that is not equivalent under it is materially different. +## Pending Task Response -The Access Request Service SHOULD retain Idempotency-Key state at least until `task.expires_at` and SHOULD continue to retain it for at least 24 hours after the task reaches a terminal status. This window lets retries from delayed PEP restarts find the original task rather than spawning a duplicate. After the retention window elapses, the Access Request Service MAY reclaim the Idempotency-Key; a submission presenting a previously seen Idempotency-Key whose state has been reclaimed is processed as a new submission. +A response with `task.status: pending` echoes the submission's Task Handle ({{task-handle-object}}). Polls use the same shape, updating status, progress, and links as the task advances. {{completed-task-response}} defines the response at terminal status. Non-normative example: ~~~ http -POST /access/v1/requests HTTP/1.1 -Host: pdp.example.com -Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA +HTTP/1.1 200 OK Content-Type: application/json -Idempotency-Key: 7b8d0f0d-65a1-4af1-9fd3-a684f08a5d13 { - "subject": { - "type": "user", - "id": "alice@example.com" - }, - "resource": { - "type": "document", - "id": "q4-plan" - }, - "action": { - "name": "can_read" - }, - "context": { - "business_justification": "Needed for customer renewal review" - }, - "requested_access": { - "requested_until": "2026-05-01T00:15:00Z" - }, - "denial": { - "evaluation_id": "eval_01HX4Y2P8BQ4Y3F0V0K9D6Z7M1", - "evaluated_at": "2026-04-30T20:15:00Z", - "expires_at": "2026-04-30T20:25:00Z", - "reason": "approval_required", - "binding_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBkcC0xIn0.eyJldmFsdWF0aW9uX2lkIjoiZXZhbF8wMUhYNFkyUDhCUTRZM0YwVjBLOUQ2WjdNMSJ9.bXBfc2lnbmF0dXJl", - "template": "manager_approval" + "task": { + "id": "arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4", + "status": "pending", + "status_endpoint": "https://pdp.example.com/access/v1/requests/arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4", + "expires_at": "2026-04-30T23:00:00Z" } } ~~~ -Non-normative bulk-submission example: +## Completed Task Response {#completed-task-response} + +The Access Request Service's rules for the result member are in {{ars-task}}. + +Non-normative example: ~~~ http -POST /access/v1/requests HTTP/1.1 -Host: pdp.example.com -Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA +HTTP/1.1 200 OK Content-Type: application/json -Idempotency-Key: 7b8d0f0d-65a1-4af1-9fd3-a684f08a5d14 { - "subject": { - "type": "user", - "id": "alice@example.com" + "task": { + "id": "arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4", + "status": "approved", + "status_endpoint": "https://pdp.example.com/access/v1/requests/arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4" }, - "items": [ - { - "resource": {"type": "document", "id": "q4-plan"}, - "action": {"name": "can_read"} - }, - { - "resource": {"type": "channel", "id": "engineering"}, - "action": {"name": "can_post"} + "result": { + "mode": "reevaluate", + "approval": { + "id": "apr_01HX4Y8E2NE3Y2X7P0K4JE6WVH", + "approved_at": "2026-04-30T20:42:00Z", + "approved_until": "2026-05-01T00:42:00Z" } - ], - "context": { - "business_justification": "Onboarding to the renewal review project" - }, - "requested_access": { - "requested_until": "2026-05-14T20:15:00Z" - }, - "denial": { - "evaluation_id": "eval_01HX4Y2P8BQ4Y3F0V0K9D6Z7M2", - "evaluated_at": "2026-04-30T20:15:00Z", - "expires_at": "2026-04-30T20:25:00Z", - "reason": "approval_required", - "binding_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBkcC0xIn0.eyJidW5kbGVfaWQiOiJidW5fMDFIWDVTVUJNMSIsIml0ZW1zIjpbeyJyZXNvdXJjZSI6ImRvY3VtZW50OnE0LXBsYW4iLCJhY3Rpb24iOiJjYW5fcmVhZCJ9LHsicmVzb3VyY2UiOiJjaGFubmVsOmVuZ2luZWVyaW5nIiwiYWN0aW9uIjoiY2FuX3Bvc3QifV19.bXBfc2lnbmF0dXJl", - "template": "onboarding_bundle" } } ~~~ -## Access Request Response {#access-request-response} - -A successful Access Request submission returns HTTP status code `201 Created` or `202 Accepted` and a JSON object containing a `task` member. The `task.status_endpoint` member is authoritative for subsequent status retrieval. A response MAY also include an HTTP `Location` header equal to `task.status_endpoint`. - -The response object has the following top-level members: - -`task`: -: REQUIRED. Task Handle returned for the submitted Access Request. - -`result`: -: OPTIONAL except where required by {{completed-task-response}}. Completion result for the task. A PEP MUST NOT treat this member as approval unless the task is approved and the result is enforceable under {{completion-semantics}}. +# Approval and Re-evaluation {#completion-semantics} -When the Access Request Service is able to resolve the request synchronously (for example, when policy auto-approves and provisioning completes within the request), the Access Request Service SHOULD return `201 Created` with `task.status` already set to a terminal value and a populated `result` member ({{completion-semantics}}). PEPs MUST handle this synchronous-completion case without polling; the Task Status Endpoint remains usable for later retrieval but is not on the critical path. +The only base completion mode, `result.mode: "reevaluate"`, directs the PEP to perform a new AuthZEN Access Evaluation after approval. The PDP evaluates current policy and state, rather than resuming the original denial, and remains authoritative at enforcement time. -The `task` object has the following members: - -`id`: -: REQUIRED. Stable, opaque, and unguessable task identifier. The value MUST contain sufficient entropy to prevent practical guessing and MUST NOT encode semantics that a PEP is expected to parse. +The PEP supplies the approval at `context.approval`; the PDP need not retain the original decision. -`status`: -: REQUIRED. Current task status. Values are defined in {{task-status}}. +Profiles of this specification MAY define additional completion modes through the `result.mode` extension point ({{extensibility}}). -`status_endpoint`: -: REQUIRED. HTTPS URI used to retrieve task status. An intermediate enforcer (such as an OAuth Authorization Server or other gateway acting as PEP) MAY proxy or re-present this endpoint to its own callers; the value advertised to such callers MAY differ from the value the PEP itself uses, provided the proxied endpoint observes the authorization rules defined for the original endpoint. +Implementations that bind approval to a specific issuance flow, such as OAuth token issuance where the issued token is itself the decision representation, MUST do so through a profile that defines a completion mode appropriate to that flow; the base profile does not define such a mode. -`progress`: -: OPTIONAL. Object describing approval workflow progress for tasks with multi-step approvals. When `items` is present, `progress` describes aggregate workflow progress for the bundled task; per-item progress is tracked in `task.items[]`. The following members are defined: +Approval platforms can use this mode by changing backing state that the next evaluation reads ({{impl-considerations}}). - * `current_step`: OPTIONAL. Integer. One-based index of the step currently in progress. - * `total_steps`: OPTIONAL. Integer. Total number of approval steps configured for the task. - * `step_name`: OPTIONAL. String. Short identifier of the current step (for example, `manager_approval` or `resource_owner_review`). - * `awaiting`: OPTIONAL. Array. Identifiers of approvers whose action is currently expected. Implementations SHOULD apply privacy controls before populating this member; see {{privacy-considerations}}. +## Approval Result {#approval-result} -`expires_at`: -: OPTIONAL. {{RFC3339}} timestamp after which the task handle is no longer valid. +`approval`: +: REQUIRED when `result.mode` is `reevaluate`. Object. Identifies the approval that completed the Access Request task and has the following members: -`display`: -: OPTIONAL. Object containing user-interface hints for the pending request. +* `id`: REQUIRED. String. Stable, opaque, and unguessable identifier of the approval. +* `approved_until`: REQUIRED. {{RFC3339}} timestamp indicating the latest time through which the approval remains valid. +* `approved_at`: OPTIONAL. {{RFC3339}} timestamp indicating when the approval completed. -`links`: -: OPTIONAL. Object containing related URLs. Each member name is a link relation type and the value is an HTTPS URI. The following relation types are defined; implementations MAY define additional relation types. +The `approval` object MAY additionally include a `state` member. `state` is an opaque JSON value populated by the Access Request Service or PDP, carrying proof or verifier state the PDP needs at re-evaluation time (for example, a signed reference, an extended lookup token, or deployment-specific state). - * `ticket`: URL where the requester (Subject) can view the request and its status. - * `review`: URL where an approver or administrator can review or act on the request. - * `cancel`: URL where the PEP can cancel the request, when PEP-initiated cancellation is supported. +The PEP's rules for carrying the approval are in {{approval-reevaluation-request}}. -`items`: -: REQUIRED when the original submission carried an `items` array; otherwise OPTIONAL. Array. Per-item progress for bundled Access Requests. Each element corresponds positionally to the submission's `items` member and has the following members: +## Re-evaluation Denials {#reevaluation-denials} - * `resource`: REQUIRED. The AuthZEN Resource for this item, echoing the submission. - * `action`: REQUIRED. The AuthZEN Action for this item. - * `status`: REQUIRED. Per-item status using the values defined in {{task-status}}. - * `result`: OPTIONAL before the item reaches a terminal status; REQUIRED when the item status is `approved`. Per-item completion result with the same shape as the top-level `result` ({{completion-semantics}}). +When the PDP denies a re-evaluation that presented an `approval` reference, it conveys what the PEP should do next through the following Decision Context members ({{pdp-reevaluation-denial}}); the PEP's handling is in {{pep-reevaluation-handling}}: -When the `items` member is present, the aggregate `task.status` is computed from per-item statuses as follows: +* `next_action`: RECOMMENDED. String. The action the PEP should take. One of: + * `request`: submit a new Access Request. + * `retry`: re-evaluate the same request after a delay; the denial is expected to be transient. This is the transient-denial outcome of the Introduction: nothing is remediated and nothing changes in the request. + * `none`: do not retry or re-request; the denial is terminal for this approval. +* `retry_after`: RECOMMENDED when `next_action` is `retry`. Integer. Number of seconds the PEP waits before re-evaluating the same request. Its value has the delta-seconds semantics of HTTP `Retry-After` (Section 10.2.3 of {{RFC9110}}); it appears in Decision Context because an Access Evaluation denial is a successful protocol response. +* `reason`: OPTIONAL. String. A machine-readable reason code for UX and audit. This profile defines the following well-known re-evaluation denial reason codes with their default `next_action`: + * `approval_expired` (`request`): the approval is no longer valid because `approved_until` has passed or it was revoked, cancelled, or superseded. + * `out_of_scope` (`request`): the approval is valid but the current evaluation falls outside its approval scope. + * `grant_pending` (`retry`): the approval is valid and in scope, but the backing entitlement, role, or grant is not yet present (for example, provisioning has not completed). + * `policy_denied` (`none`): the approval is valid and in scope, but current policy, subject status, or risk state denies the request; re-requesting will not help. + * `approval_unverifiable` (`none`): the presented `approval.id` or `approval.state` could not be resolved or verified, or its binding did not match. -* If any item is `pending` or in an implementation-defined non-terminal status ({{task-status}}), the aggregate is `pending`. -* Otherwise, if all items share the same terminal status, the aggregate is that status. -* Otherwise, with two or more distinct terminal statuses present across items, the aggregate is `partial`. + Implementations MAY register additional reason codes (for example, a more specific `approval_revoked`). These codes are registered in the AuthZEN Access Request Re-evaluation Denial Reason registry ({{iana-reeval-reasons}}). -A PEP processing a bundled task MUST consult `task.items[].status` and `task.items[].result` to determine per-item outcomes; the PEP MUST NOT infer per-item outcomes from the aggregate `task.status` alone. A top-level `result` MUST NOT be used to authorize any individual item in a bundled task unless the same result is also present in that item's `result` member. +## Re-evaluation Example {#lookup-reevaluation-example} -Non-normative example: +This non-normative exchange uses `approval.id` to resolve the approval from trusted server-side state ({{approval-reference-lookup}}). -~~~ http -HTTP/1.1 202 Accepted -Content-Type: application/json -Location: https://pdp.example.com/access/v1/requests/arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4 +Non-normative re-evaluation request: +~~~ json { - "task": { - "id": "arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4", - "status": "pending", - "status_endpoint": "https://pdp.example.com/access/v1/requests/arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4", - "expires_at": "2026-04-30T23:00:00Z", - "links": { - "cancel": "https://pdp.example.com/access/v1/requests/arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4/cancel" + "subject": { + "type": "user", + "id": "alice@example.com" + }, + "resource": { + "type": "document", + "id": "q4-plan" + }, + "action": { + "name": "can_read" + }, + "context": { + "approval": { + "id": "apr_01HX4Y8E2NE3Y2X7P0K4JE6WVH", + "approved_at": "2026-04-30T20:42:00Z", + "approved_until": "2026-05-01T00:42:00Z" }, - "display": { - "title": "Access request submitted", - "description": "Your manager has been asked to approve access." - } + "time": "2026-04-30T20:43:00Z" } } ~~~ -Non-normative synchronous-completion example, where policy auto-approved the request: - -~~~ http -HTTP/1.1 201 Created -Content-Type: application/json -Location: https://pdp.example.com/access/v1/requests/arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V5 +Non-normative re-evaluation response: +~~~ json { - "task": { - "id": "arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V5", - "status": "approved", - "status_endpoint": "https://pdp.example.com/access/v1/requests/arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V5" - }, - "result": { - "mode": "reevaluate", + "decision": true, + "context": { "approval": { - "id": "apr_01HX4Y8E2NE3Y2X7P0K4JE6WVJ", + "id": "apr_01HX4Y8E2NE3Y2X7P0K4JE6WVH", "approved_until": "2026-05-01T00:42:00Z" } } } ~~~ -# Task Status Endpoint {#task-status-endpoint} +# Error Responses {#error-responses} + +HTTP error responses from the Access Request Endpoint and Task Status Endpoint MUST use `application/problem+json` as defined by {{RFC9457}} when returning one of the problem types defined by this specification. The problem type URI MUST appear in the `type` member. + +The following problem types are defined: + +`urn:openid:authzen:access-request:error:not_requestable`: +: HTTP `400 Bad Request`. The submitted denial is not requestable. + +`urn:openid:authzen:access-request:error:expired_denial`: +: HTTP `410 Gone`. The requestable denial has expired: the freshness deadline (the earlier of `denial.expires_at` and any `binding_token` `exp`) has passed. + +`urn:openid:authzen:access-request:error:invalid_denial_binding`: +: HTTP `400 Bad Request`. The submitted Access Request cannot be bound to the denied AuthZEN Decision. -The Task Status Endpoint allows the PEP to retrieve the state of a previously submitted Access Request. +`urn:openid:authzen:access-request:error:duplicate_request`: +: HTTP `409 Conflict`. The `Idempotency-Key` was reused by the same requester with a submission body that is not equivalent to the original request (see {{submission-idempotency}}). + +`urn:openid:authzen:access-request:error:unknown_task`: +: HTTP `404 Not Found`. The task handle is unknown or unavailable to the caller. -The PEP calls the `status_endpoint` using the HTTP `GET` method as defined in {{RFC9110}}. +`urn:openid:authzen:access-request:error:task_expired`: +: HTTP `410 Gone`. The task handle has expired. -The Task Handle is portable across PEP instances and process lifetimes. A caller MAY interact with the Task Handle, such as polling status or initiating cancellation, even if that caller is not the original submitting PEP, provided the caller is authorized for the original Subject, Resource, Action, task, and requested operation. This supports PEP restart, replacement, and agent-runtime handoff, where the Task Handle flows through application context (such as a conversation thread, a session store, or a workflow orchestrator). This profile does not define an enumeration API for tasks belonging to a Subject; Task Handles are exchanged through the channels by which the original Access Request response was delivered. +`urn:openid:authzen:access-request:error:invalid_task_state`: +: HTTP `409 Conflict`. The requested operation cannot be performed in the current task state (for example, cancellation of a task that has already reached a terminal status). Non-normative example: ~~~ http -GET /access/v1/requests/arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4 HTTP/1.1 -Host: pdp.example.com -Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA -Accept: application/json +HTTP/1.1 400 Bad Request +Content-Type: application/problem+json + +{ + "type": "urn:openid:authzen:access-request:error:not_requestable", + "title": "Access is not requestable", + "status": 400, + "detail": "The denied decision did not contain a context.access_request object." +} ~~~ -A successful response returns a JSON object containing a `task` member. Completed task responses include a `result` member according to the rules in {{completed-task-response}}. +# Binding Integrity {#deployment-alternatives} -When a task is `pending`, a PEP MAY poll the Task Status Endpoint to determine completion. PEPs SHOULD use exponential backoff: a starting interval of several seconds, growing to no more than one minute, with jitter applied to spread load across many concurrent pollers. If the Access Request Service returns the `Retry-After` HTTP header (Section 10.2.3 of {{RFC9110}}), the PEP MUST wait at least the indicated duration before issuing the next poll. The PEP MUST stop polling once `task.expires_at` is reached or the task reaches a terminal status ({{state-transitions}}). PEPs subscribed to per-task callbacks ({{callback-completion}}) or to deployment-level event subscriptions MAY skip polling entirely and rely on push notification, falling back to a single status retrieval after each notification to obtain any enforceable `result`. +Denial binding connects an Access Request to the denied evaluation; approval binding connects a re-evaluation to the approved request. Both use the comparison rules in this section. -## Task Status Values {#task-status} +## Structural Comparison {#structural-comparison} -The following task status values are defined: +Throughout this profile, structural comparison requires the same JSON type and applies these rules: -`pending`: -: The request has been accepted and is awaiting processing or approval. +* Numbers are equal under their {{RFC8785}} canonical form. +* Strings are equal codepoint-for-codepoint. +* Arrays are equal element-by-element in order. +* Objects have the same set of member names with recursively equal values. +* An absent member is distinct from a member whose value is `null`. -`approved`: -: The request was approved. Approval does not by itself grant access unless accompanied by a result that can be enforced under {{completion-semantics}}. +These rules apply wherever the profile compares Subject, Resource, Action, or authorization-relevant Context, including inline denial binding and approval-scope matching ({{approval-scope}}). -`denied`: -: The request was denied by the approval workflow. +For inline denial binding and exact-match approval-scope matching, Subject, Resource, and Action are compared as full AuthZEN Authorization API objects, every `properties` member present in the bound values included, except that `subject.properties.act` is excluded because the PEP MAY normalize the actor to `client.actor` (see {{pep-processing-rules}} and {{ACTOR}}); the exclusion applies whether or not the actor profile is implemented, so two core implementations compare identically. Context comparison includes each member of the authorization-relevant Context and excludes profile machinery members. -`expired`: -: The request expired before completion. +Denial binding, approval-scope matching, and idempotent-submission comparison all compare the authorization-relevant Context, so when any member is authorization-relevant the PDP MUST make it explicit and integrity-protected, and the Access Request Service MUST use exactly that set: -`cancelled`: -: The request was cancelled by the requester, approver, administrator, or system. +- with a `binding_token`, the token carries the set as a `binding_context_members` claim ({{binding-token-integrity}}); +- with `evaluation_id`, the set is recorded for that evaluation in shared state and resolved server-side. -`failed`: -: The request could not be completed due to an error. +Absent an integrity-protected or server-resolved set, the authorization-relevant Context is empty and only Subject, Resource, and Action bind. -`partial`: -: All items in a bulk task ({{access-request-response}}) reached terminal status, but with mixed outcomes (for example, some items approved while others denied). This status is only valid for tasks containing an `items` array. A PEP receiving `partial` MUST consult `task.items[].status` to determine per-item outcomes and MUST NOT infer aggregate access permission. +Profile machinery members (`access_request`, `evaluation_id`, `evaluated_at`, `reason`, and `approval`) are not authorization-relevant, and a PDP SHOULD also exclude volatile members (timestamps, nonces, or request identifiers such as `context.time`) so the authorization-relevant Context set compares equal across the denial and a later submission or re-evaluation. -Implementations MAY define additional status values. A PEP that receives an unknown status value MUST treat the task as not approved. +## Decision and Binding Integrity {#decision-and-binding-integrity} -### State Transitions {#state-transitions} +Implementations MUST bind Access Requests and approval results to the Subject, Resource, Action, Context, task, and requester. PDPs MUST validate this binding during re-evaluation. -In the base state machine, a task is created in the `pending` state and transitions exactly once to one of the terminal states defined above. Terminal states do not transition further. +Possession of a valid-looking approval identifier is insufficient to authorize access; the applicability check is stated in {{approval-verification}}. -~~~ ascii-art - (created) - | - v - +-------------+ - | pending | - +------+------+ - | - +----------+----------+----+----+-----------+-----------+ - | | | | | | - v v v v v v -+--------+ +------+ +-------+ +----------+ +--------+ +---------+ -|approved| |denied| |expired| |cancelled | | failed | | partial | -+--------+ +------+ +-------+ +----------+ +--------+ +---------+ - (bulk only) -~~~ +When approval state is carried by reference, the PDP or Access Request Service MUST protect the backing approval record against unauthorized lookup and mutation. When approval binding material is carried by value, for example in `approval.state`, the PDP MUST verify integrity, issuer, audience or intended recipient, expiry, and binding before accepting it. -The following transitions are defined from `pending`: +Approval results MUST expire. Under the `reevaluate` completion mode, approval references SHOULD be bound to the original request tuple. Profiles of this specification that define token-based completion modes are responsible for defining the token's audience restriction, lifetime, and binding to the approved request. -| To | Trigger | -|---|---| -| `approved` | Approval workflow completes successfully. | -| `denied` | Approval workflow rejects the request. | -| `expired` | `task.expires_at` is reached before the request reaches a terminal state. | -| `cancelled` | The request is cancelled by the requester, approver, administrator, or PEP using the cancellation endpoint ({{cancellation}}). | -| `failed` | A system error prevents the request from completing. | -| `partial` | Bulk tasks only. All items in the `items` array reach terminal status, with two or more distinct terminal statuses present. See aggregation rules in {{access-request-response}}. | +## Time and Clock Skew {#time-and-clock-skew} -For tasks containing an `items` array, each item independently follows the same base state machine; the aggregate `task.status` is computed from per-item statuses according to the aggregation rule in {{access-request-response}}. +The {{RFC3339}} timestamps in `context.evaluated_at`, `context.access_request.expires_at`, `denial.expires_at`, `task.expires_at`, `approval.approved_at`, and `approval.approved_until` may be checked on a host other than their producer. Clock skew can therefore cause incorrect freshness or expiry decisions. -Implementations that define additional status values ({{task-status}}) extend the state machine. Such extensions SHOULD specify the transitions into and out of the new state and document them alongside the value definition. +Implementations SHOULD allow a small skew tolerance when comparing a remote-host timestamp against the local clock. A tolerance of 30 seconds is typical; tolerances above 60 seconds are NOT RECOMMENDED. A PEP comparing `approval.approved_until` to local time MAY treat the approval as valid until `approved_until` plus the tolerance. An Access Request Service comparing `denial.expires_at` (the PEP-echoed requestable-denial hint expiry) to its local clock MAY accept submissions arriving up to the tolerance after that timestamp, after verifying the echoed value against the denial-binding material. + +Hosts that produce timestamps SHOULD synchronize their clocks against a reliable time source (for example, NTP or PTP) to keep skew well below the tolerance window. Deployments with stricter requirements (for example, regulatory or audit constraints) MAY define a tighter tolerance and document it as part of their deployment profile. -### Mapping Backend States {#status-mapping} +# PEP Processing {#pep-processing-rules} -Access Request Services typically maintain richer task lifecycle state than the canonical statuses defined above. Common backend models include separate fields for open versus closed, processing versus waiting, escalation states, and auto-approval states. Implementations are expected to collapse such richer state into the canonical statuses for the purpose of the Task Status Endpoint. +This section holds every rule for a PEP implementing this profile, in the order of the flow in {{protocol-overview}}. The messages it sends and receives are defined in {{requestable-denial-context}}, {{access-request-submission}}, {{task-handle-object}}, and {{completion-semantics}}. -The following non-normative mapping illustrates one such collapse and may be used as a starting point: +## Recognize the Denial {#pep-recognize} -| Backend state | Canonical status | +* MUST treat `decision: false` as a denial, even when the Decision Context contains an `access_request` object. +* MUST treat the absence of `context.access_request` as a non-requestable denial regardless of any other context members. +* MUST submit an Access Request only for an AuthZEN Decision with `decision` equal to `false` and a `context.access_request` object present in the Decision Context. + +## Check the URLs {#trusting-urls} + +The following checks apply to `endpoint`, `form_url`, `request_schema_url`, and any URL a profile adds to the requestable denial or to a document fetched from it. + +An autonomous PEP MUST verify that these URLs resolve to hosts trusted under the deployment before fetching or acting on them, by requiring the same origin as the Access Request Endpoint advertised in PDP metadata or by maintaining an explicit allowlist of trusted Access Request Service hosts. +A PEP that renders them for a human user SHOULD apply the same check. + +A PEP that performs the origin check obtains the Access Request Endpoint from PDP metadata even when the denial supplies `endpoint`. When a URL fails the check, the PEP does not fetch it or submit to it; what it then reports to a requester is a deployment matter. + +PEPs MUST NOT submit credentials to a host that is not trusted to receive them. + +## Construct the Submission {#pep-construct} + +* MUST use the `endpoint` from the denial context when present; if `endpoint` is omitted, MUST use the `access_request_endpoint` from PDP metadata ({{discovery}}). +* Echoes every `denial` member the PDP supplied, unchanged: `expires_at`, `evaluation_id`, `binding_token`, `evaluated_at`, `reason`, and `template` ({{submission-denial-object}}). +* MUST include `denial.evaluation_id` when `denial.binding_token` is absent, and SHOULD include it when the PDP returned an evaluation identifier. `evaluation_id` provides the strongest audit binding between the original denial and the submitted Access Request and SHOULD be preferred over `evaluated_at` alone. +* MUST NOT decode, modify, or interpret `binding_token`; it returns the original PDP-issued value byte-for-byte as `denial.binding_token`. +* MUST NOT interpret the `template` value except for display or request submission; the value is not a policy language. +* MAY ignore `display`. + +The PEP MUST preserve the principal identity of the Subject, and MUST preserve the Resource, Action, and relevant Context of the denied evaluation when submitting the Access Request. When the original evaluation conveyed an actor identity in the Subject (for example, via `subject.properties.act`), the PEP MAY preserve the actor in the submission's `subject` or normalize it to `client.actor` ({{ACTOR}}); the actor identity itself MUST NOT be dropped. + +Submission-time augmentations MUST NOT change or remove authorization-relevant context from the denied evaluation. When the Access Request Service needs to distinguish original evaluation context from submission-time input, deployments SHOULD place the latter in well-defined extension members rather than overwriting original context members. + +When the requestable denial includes `request_schema_url`, the PEP MUST construct the augmentations to the submission's `context` and `requested_access` objects according to {{machine-readable-forms}}. If the schema requires information the PEP cannot obtain or is not authorized to supply, the PEP MUST NOT fabricate values or submit an incomplete request; it MUST either surface the request for additional input, hand it to another authorized component, or treat the denial as not requestable by that PEP. + +A PEP SHOULD include an `Idempotency-Key` header, following the conventions described in {{I-D.ietf-httpapi-idempotency-key-header}}. + +## Handle the Response {#pep-submit} + +* MUST treat a Task Handle as opaque. +* MUST NOT infer approval from a task identifier, link, or display text. +* MUST NOT treat `result` as approval unless the task is approved and the result is enforceable under {{completion-semantics}}. +* MUST handle synchronous completion, a submission response whose `task.status` is already terminal, without polling; the Task Status Endpoint remains usable for later retrieval but is not on the critical path. + +An intermediate enforcer (such as an OAuth Authorization Server or other gateway acting as PEP) MAY proxy or re-present `status_endpoint` to its own callers; the value advertised to such callers MAY differ from the value the PEP itself uses, provided the proxied endpoint observes the authorization rules defined for the original endpoint. + +## Poll the Task {#pep-poll} + +* When a task is `pending`, a PEP MAY poll the Task Status Endpoint to determine completion. +* PEPs SHOULD use exponential backoff: a starting interval of several seconds, growing to no more than one minute, with jitter applied to spread load across many concurrent pollers. +* If the Access Request Service returns the `Retry-After` HTTP header (Section 10.2.3 of {{RFC9110}}), the PEP MUST wait at least the indicated duration before issuing the next poll. +* The PEP MUST stop polling once `task.expires_at` is reached or the task reaches a terminal status ({{state-transitions}}). +* A PEP that receives an unknown status value MUST treat the task as not approved. +* SHOULD fail closed when task status cannot be determined. + +## Handle Completion {#pep-complete} + +* MUST enforce an approved result only according to {{completion-semantics}}. +* For any terminal status other than `approved`, MUST NOT treat a `result` object as approval. +* A PEP that receives an unknown `result.mode` value MUST treat the task as not approved and MUST NOT permit access on the basis of that result. +* MUST re-evaluate access through the AuthZEN Access Evaluation API after approval, unless a profile-defined completion mode applies (for example, a profile binding to OAuth token issuance). + +## Re-evaluate {#approval-reevaluation-request} + +The PEP MUST include the `approval` object unchanged at `context.approval` inside the AuthZEN Authorization API re-evaluation request. This includes its `id`, timestamps, and any `state`. + +The PEP MUST preserve the JSON value exactly and MUST NOT modify or interpret the contents of `approval.state`. + +The PEP MUST NOT use the approval for re-evaluation after `approved_until`. + +## Handle a Re-evaluation Denial {#pep-reevaluation-handling} + +A PEP handles `next_action` as follows: + +* A PEP MUST drive its behavior from `next_action` when the value is recognized. +* A PEP that receives `next_action: "request"` without `context.access_request` MUST NOT submit a new Access Request and treats the denial as `none`. +* A PEP that receives no `next_action`, or an unrecognized value, falls back first to the default next action for a recognized `reason`, then to the requestable-denial signal. +* When the fallback action is `request`, the PEP treats the denial as `request` only when `context.access_request` is present; otherwise it treats the denial as `none`. + +A PEP that receives `next_action: "retry"` without `retry_after` SHOULD apply bounded exponential backoff with jitter, starting at several seconds and growing to no more than one minute between attempts, and MUST stop retrying once the approval expires. + +A PEP uses a recognized `reason`'s registered default only when `next_action` is absent or unrecognized. A PEP treats an unrecognized `reason` as informational and relies on `next_action` or the fallback rule above. + +This non-normative table summarizes the fallback order defined above. Use the first matching row. + +| Decision Context | Action source | |---|---| -| Open, awaiting approval or processing | `pending` | -| Closed, all required approval steps satisfied | `approved` | -| Closed, an approval step rejected the request | `denied` | -| Closed, time-bounded request elapsed before completion | `expired` | -| Closed, requester or administrator stopped the request | `cancelled` | -| Closed, system error prevented completion | `failed` | -| Closed, items in a bulk task reached two or more distinct terminal statuses | `partial` | +| Recognized `next_action` | Use `next_action`, even if a reason code suggests a different default. | +| Absent or unrecognized `next_action`, with a recognized `reason` | Use the reason's default next action. | +| Neither a recognized `next_action` nor a recognized `reason` | Use the requestable-denial signal: `request` when `context.access_request` is present, otherwise `none`. | -Implementations SHOULD document the mapping they apply so that PEP behavior remains predictable across upgrades and operational changes. +## Enforce and Reuse the Approval {#approval-lifetime} -## Pending Task Response +The `approved_until` timestamp in the Approval Result envelope is a PEP-side maximum reuse and enforcement bound; it does not prevent the PDP from denying earlier because of revocation, cancellation, policy change, risk change, or other current state. The two roles read the same value differently: the PEP applies the envelope value conservatively, and it can only shorten the PEP's use of the result, while the PDP's authoritative expiry comes from trusted state or from integrity-protected approval material ({{approval-verification}}). -A response with `task.status: pending` echoes the Task Handle returned at submission ({{access-request-response}}). Subsequent polls return the same response shape with status, progress, and link members updated as the task advances; when the task reaches a terminal status, the response form is governed by {{completed-task-response}}. +When the re-evaluation response indicates an approval expiry (typically as `context.approval.approved_until`), the PEP MUST NOT enforce access past that timestamp. PEPs that issue downstream credentials on the basis of the approved evaluation (for example, an OAuth Authorization Server issuing access tokens) MUST bound the lifetime of those credentials by the earlier of the approval expiry in the Approval Result and any approval expiry returned by the PDP during re-evaluation. Expiry inside an opaque artifact is checked by its verifier, not extracted by the PEP ({{verifying-denial-binding}}, {{approval-verification}}). -Non-normative example: +### Approval Reuse {#approval-reuse} -~~~ http -HTTP/1.1 200 OK -Content-Type: application/json +The PDP determines whether an approval applies to each evaluation ({{approval-scope}}). -{ - "task": { - "id": "arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4", - "status": "pending", - "status_endpoint": "https://pdp.example.com/access/v1/requests/arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4", - "expires_at": "2026-04-30T23:00:00Z" - } -} -~~~ +A PEP MUST NOT treat an Approval Result as authorizing any future Access Evaluation solely on the basis that the Access Request was approved. + +A PEP MAY include the Approval Result in a subsequent Access Evaluation (by placing the `approval` object at `context.approval`), but the PDP remains responsible for determining whether the Approval Result applies under current policy. + +A PEP MAY cache or retain an Approval Result, but MUST NOT independently infer that a future request is covered by that approval unless directed by the PDP or by a profile-defined mechanism. + +## Task Handle Portability {#task-handle-portability} + +The Task Handle survives PEP restart, replacement, or handoff to another runtime instance. A caller MAY interact with the Task Handle, such as polling status or initiating cancellation, even if that caller is not the original submitting PEP, provided the caller is authorized for the original Subject, Resource, Action, task, and requested operation. + +Refresh of a calling identity's underlying token does not invalidate Task Handle access as long as the caller remains authorized for the bound task and operation. A different PEP instance or agent process that can authenticate as authorized for the bound task and operation MAY interact with the Task Handle. + +For example, an agent can persist the Task Handle at step 4 of {{protocol-overview}} and resume at step 5 or 6 from a fresh process. A conversation thread, session store, or workflow orchestrator can carry the handle without preserving the original session. + +This profile defines neither pause/resume orchestration nor an API to enumerate a Subject's tasks. Task Handles are exchanged through the channels that delivered the original Access Request response. + +## Surfaces and Forwarding {#pep-facing-surfaces} + +The following members are for PEP interactions with the Access Request Service or PDP, not for direct use by end clients such as browsers, mobile applications, or agent runtime UIs: + +* `task.status_endpoint`: the polling URL for the Access Request Service. +* `task.links.cancel`: the cancellation endpoint. +* `approval.id` and `approval.state`: round-trip material the PEP places at `context.approval` during re-evaluation. + +PEPs SHOULD NOT forward these members to end clients or other non-PEP callers. Exposing them enables direct service calls that bypass the PEP or attempts to inject approval references into other evaluations. Possession is not authorization: Task Handle operations require caller authorization ({{authorization-and-authentication}}), and the PDP checks approval applicability ({{approval-verification}}). + +Human-facing members are intended only for callers authorized for the corresponding workflow: + +* `task.links.ticket`: URL where the requester (Subject) can view the request and its status. +* `task.links.review`: URL where an approver or administrator can review or act on the request. +* `task.display`: localizable user-interface hints. + +When a PEP renders requester-facing status to an end client, it SHOULD do so by rendering `task.display` and `task.links.ticket` rather than by exposing the machine surfaces. A PEP MUST NOT expose `task.links.review` to a requester or other end client unless that caller has been authenticated and authorized as an approver or administrator for the task. + +Cancellation ({{cancellation}}), actor delegation ({{ACTOR}}), and machine-readable forms ({{machine-readable-forms}}) define further PEP rules where those mechanisms are used. + +# PDP Processing {#pdp-processing} + +This section holds every rule for a PDP implementing this profile. + +## Publish Metadata {#pdp-metadata-rules} + +A PDP supporting this profile MUST publish an `access_request_endpoint` in PDP metadata. The endpoint value MUST be an HTTPS URI. + +A PDP supporting this profile SHOULD include the following capability URN in the `capabilities` array: + +`urn:openid:authzen:capability:access-request` + +The `access_request_endpoint` MAY be hosted by the PDP itself, by a service trusted by the PDP, or by an independent service operating with delegated authority from the PDP. When hosted by a different service, the PDP metadata MUST identify the endpoint actually used by the PEP to submit access requests. + +## Issue a Requestable Denial {#denial-issuance} + +A PDP implementing this profile: + +* MAY include `context.access_request` in a denied AuthZEN Decision when the denied access is eligible for approval. +* MUST NOT include `context.access_request` unless the denied access is eligible for submission and an Access Request Endpoint is available to process the request. +* SHOULD include a stable machine-readable reason code when returning a requestable denial. +* MUST include an expiration time for the requestable denial hint as `context.access_request.expires_at`. +* MUST provide verifiable denial-binding material when returning `context.access_request`: an integrity-protected `context.access_request.binding_token`, or a stable `context.evaluation_id` the Access Request Service can resolve against state shared with, or delegated by, the PDP. When the Access Request Service is independent of the PDP, the PDP MUST provide the `binding_token` form ({{requestable-denial-context}}). +* SHOULD return a stable evaluation identifier as `context.evaluation_id` ({{evaluation-identifier}}) that the PEP can supply as `denial.evaluation_id` when submitting an Access Request. + +The PDP MUST provide enough denial-binding material for the Access Request Service to verify that a submitted Access Request corresponds to the denied evaluation and is still fresh. A requestable denial MUST include `expires_at` and at least one of two denial-binding forms: `binding_token` by value, defined in {{denial-binding}}, or `evaluation_id` by reference, defined in {{shared-state-deployments}}. + +In the by-reference form, the durable denial-binding record lives in the Access Request Service role, not the PDP; a self-contained `binding_token` needs no record before submission. This is the denial-side mirror of the re-evaluation rule that requires `approval.state` when the PDP cannot resolve `approval.id` from shared or delegated state ({{approval-state}}). When neither binding form is available, or when the Access Request Service cannot determine that the binding is unexpired, the PDP MUST NOT include `context.access_request` in the Decision Context. + +`evaluation_id` MUST be stable for a given evaluation: subsequent retrievals or echoes of the same evaluation MUST return the same identifier. PDPs SHOULD generate identifiers that are unique within the PDP's namespace (for example, ULIDs or UUIDs). + +An identifier MAY be reused across distinct evaluations only after the original evaluation's binding window has expired. The binding window is the period during which the Access Request Service can resolve or validate the identifier for Access Request submission; it MUST NOT extend beyond `context.access_request.expires_at`. + +A PDP that returns `context.access_request` without an integrity-protected `binding_token` MUST include `evaluation_id` so the Access Request Service has verifiable denial-binding material; this is the by-reference form and applies only to shared-state deployments ({{requestable-denial-context}}). + +### When Binding Artifacts Are Used {#pdp-artifact-conformance} + +For a PDP using the mechanisms in {{binding-artifacts}}: + +* When including `context.access_request.binding_token`, MUST integrity-protect it using a mechanism the Access Request Service can verify and SHOULD issue it as a JWS in compact serialization. + +## Verify the Approval at Re-evaluation {#pdp-verify} + +The PDP MUST evaluate the new request using current policy and the approval reference. The PDP MAY still deny access if policy, subject, resource, action, context, approval lifetime, or risk state no longer permits access. The PDP MUST ensure that approval does not override policy conditions that remain mandatory at enforcement time, such as subject status, resource sensitivity, action constraints, environmental risk, and approval expiry. + +### Approval Verification {#approval-verification} + +The `approval` object, not the original `evaluation_id`, links re-evaluation to the approved Access Request and original denial. + +The PDP MUST be able to resolve or verify `approval.id`, `approval.state`, or both, and bind the approval to the Access Request task, the original denied evaluation when recorded, the approved Subject, Resource, Action, relevant Context, approval scope, and approval expiry. + +When both `approval.id` and an integrity-protected `approval.state` are present and `approval.state` carries its own approval identifier, the PDP MUST verify that the two identifiers match, and MUST reject the re-evaluation on mismatch. -## Completed Task Response {#completed-task-response} +At re-evaluation, the PDP: -A completed task response includes result information as follows: +* MUST NOT authorize a re-evaluation solely because the request contains a known `approval.id`. +* MUST resolve or verify the approval reference presented in `context.approval` and confirm that it is applicable to the authenticated caller or requester, current Subject, Resource, Action, relevant Context, approval scope, and approval expiry before using it as an input to an allow decision. +* MUST ignore or reject a swapped, replayed, expired, or otherwise non-applicable approval reference, and MUST evaluate the request as not approved by that reference. -* When `task.status` is `approved` and the task does not contain an `items` array, the response MUST include a top-level `result` object. -* When `task.status` is `approved` and the task contains an `items` array, each approved item in `task.items[]` MUST include its own `result` object. The response MAY also include a top-level `result` object for aggregate workflow information, but a PEP MUST NOT use that top-level `result` to authorize an individual item unless the same result is also present in that item's `result` member. -* For any other terminal status, the response MAY include a `result` object for diagnostic or workflow information, but the PEP MUST NOT treat it as approval. -* When present, the `result` object MUST use one of the completion forms defined in {{completion-semantics}}. +Values carried outside `approval.state`, including `approval.id`, `approved_at`, and `approved_until`, MUST NOT be treated as authoritative by the PDP unless resolved from trusted state or proven by integrity-protected approval binding material. -A task remains retrievable from the Task Status Endpoint after it has reached a terminal status, until `task.expires_at` is reached or the Access Request Service removes it according to local retention policy. After expiry or removal, the Task Status Endpoint MUST return `urn:openid:authzen:access-request:error:task_expired` or `urn:openid:authzen:access-request:error:unknown_task` as appropriate. +Deployments MAY use lookup of `approval.id` and verification of `approval.state` together. In all cases, the PDP MUST verify the approval against trusted state or integrity-protected binding material; neither `approval.id` nor `approval.state` is a bearer grant by itself. -Cancellation of a pending Access Request MAY be performed by the Access Request Service, the requester through a separate user interface, an approver, or the PEP using the cancellation endpoint defined in {{cancellation}}. +The approval record or verifiable binding material MUST contain, or allow the PDP to determine, at least the approval identifier, Access Request task identifier, original denied evaluation identifier when available, approved Subject, approved Resource and Action or approval scope, requester and client binding, approval status, `approved_at` when available, `approved_until`, and any revocation or cancellation state. -Non-normative example: +### Approval Reference by Lookup {#approval-reference-lookup} -~~~ http -HTTP/1.1 200 OK -Content-Type: application/json +The PDP resolves `approval.id` in trusted server-side state. {{approval-state}} defines the bound-reference alternative using `approval.state`. -{ - "task": { - "id": "arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4", - "status": "approved" - }, - "result": { - "mode": "reevaluate", - "approval": { - "id": "apr_01HX4Y8E2NE3Y2X7P0K4JE6WVH", - "approved_at": "2026-04-30T20:42:00Z", - "approved_until": "2026-05-01T00:42:00Z", - "state": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImFycy0xIn0.eyJhcHByb3ZhbF9pZCI6ImFwcl8wMUhYNFk4RTJORTNZMlg3UDBLNEpFNldWSCJ9.c2lnbmF0dXJl" - } - } -} -~~~ +### Current Approval Status {#approval-current-status} -## Cancellation {#cancellation} +The PDP MUST check current approval status during re-evaluation, including whether the approval has been revoked, cancelled, superseded, or otherwise invalidated before `approved_until`. Stateless PDP evaluation means retaining no prior decisions, not dispensing with this check. -An Access Request Service MAY support PEP-initiated cancellation of a pending Access Request. When supported, the Task Handle MUST include a `links.cancel` member. The PEP cancels by issuing an HTTP `POST` to `links.cancel`; implementations MAY also accept HTTP `DELETE` against `links.cancel` as an equivalent cancellation request. +### Approval Scope {#approval-scope} -The cancellation request body is an OPTIONAL JSON object with the following members: +Approval scope determines which Access Evaluations may use an approval, subject to current PDP policy. Reusing an approval avoids a new Access Request for each operation. This profile defines exact-match scope; broader matching, such as a resource-class or role grant, is deployment-specific or defined by downstream profiles ({{approval-scope-extensions}}). -`reason`: -: OPTIONAL. String. Stable, machine-readable reason code. +The default approval scope is the original denied Subject, Resource, Action, and authorization-relevant Context bound to the Access Request. An evaluation is within this scope when its Subject, Resource, Action, and authorization-relevant Context are equal, member by member, to the bound values, using the structural comparison, authorization-relevant Context set, and `subject.properties.act` exclusion defined in {{structural-comparison}}. This baseline is engine-neutral, and two independently implemented PDP and Access Request Service pairs MUST interoperate on it. -`comment`: -: OPTIONAL. String. Human-readable cancellation note for audit. +The exact-match baseline is the default unless the Access Request Service or PDP records a broader or narrower approval scope ({{approval-scope-extensions}}). This default scope is not serialized in the Approval Result unless a profile or deployment defines a representation for it. -A successful cancellation returns `200 OK` and the updated `task` object whose `status` is `cancelled`. Cancellation of a task that has already reached a terminal status returns `409 Conflict` using the `urn:openid:authzen:access-request:error:invalid_task_state` problem type. +The PDP MUST only consider an Approval Result applicable when the current evaluation request is within the approval scope recorded for that Approval Result. -The Access Request Service MUST authenticate the PEP and MUST verify the PEP is authorized for the original Subject, Resource, Action, task, and cancellation operation. Authorization to submit the original request or to act for the Subject does not by itself authorize cancellation; the service MUST verify authorization for the bound Resource, Action, task, and operation. +#### Approval Scope Extensions {#approval-scope-extensions} -An Access Request Service that does not support PEP-initiated cancellation omits `links.cancel`; a cancellation attempted at any cancellation endpoint in such a deployment returns `405 Method Not Allowed`. +Broader approvals can cover a resource class, role, entitlement, or time-bounded tool class. Their representation and matching are deployment-specific or defined by downstream profiles, not portable across policy engines. This profile does not define context-constraint matching. -For a task containing an `items` array, cancellation cancels every item currently in `pending` status; items already in a terminal status remain unchanged. Behavior for items in implementation-defined non-terminal statuses ({{task-status}}) is implementation-defined; an Access Request Service that defines additional non-terminal statuses SHOULD document whether cancellation transitions those items to `cancelled` or leaves them unchanged. The aggregate `task.status` is recomputed according to the aggregation rule defined in {{access-request-response}}, which yields `cancelled` when no item completed before cancellation, or `partial` when some items reached other terminal statuses first. Cancellation of a bulk task in which every item is already in a terminal status returns `409 Conflict` with `urn:openid:authzen:access-request:error:invalid_task_state`. +The Access Request Service's workflow policy determines approval breadth, subject to this profile's integrity, expiry, and audit requirements. -PEPs that need to abandon an outstanding request without using this endpoint MAY stop polling and rely on `task.expires_at` and Access Request Service expiry to release resources. +## Deny a Re-evaluation {#pdp-reevaluation-denial} -# Completion Semantics {#completion-semantics} +When the PDP denies a re-evaluation that presented an `approval` reference, it SHOULD tell the PEP what to do next using the Decision Context members defined in {{reevaluation-denials}}. -This profile defines a single completion mode, identified by `result.mode`: `reevaluate`. The mode instructs the PEP to perform a new AuthZEN Access Evaluation after approval, so the PDP remains authoritative at enforcement time. +When `next_action` is `request`, the PDP MUST also include a fresh `context.access_request` ({{requestable-denial-context}}) so the PEP has a valid requestable-denial signal for the new submission. -Re-evaluation does not require the PDP to retain decision state from the original denial. The PDP treats the approval as an input attribute: the PEP carries the `approval` object into the new Access Evaluation at `context.approval`, and the PDP reads it the way it reads any other backing attribute, such as a role, group membership, or risk signal. The new evaluation runs against current policy and current state; it is not a resumption of the earlier evaluation. Whether the re-evaluation must reach the same PDP is a property of the binding topology, not of this profile: an integrity-protected `approval.state` can be verified by any PDP that holds the issuer's verification key, while an `approval.id` resolved against server-side state requires a PDP that shares that state. +# Access Request Service Processing {#ars-processing-rules} -Profiles of this specification MAY define additional completion modes through the `result.mode` extension point ({{extensibility}}). Implementations that bind approval to a specific issuance flow, such as OAuth token issuance where the issued token is itself the decision representation, MUST do so through a profile that defines a completion mode appropriate to that flow; the base profile does not define such a mode. A PEP that receives an unknown `result.mode` value MUST treat the task as not approved and MUST NOT permit access on the basis of that result. +This section holds every rule for an Access Request Service implementing this profile. -Most existing approval, IGA, and ITSM systems map naturally onto Re-evaluation Mode: approval changes state in a backing system, and a subsequent AuthZEN Authorization API evaluation reflects that state. See {{impl-considerations}} for deployment patterns that adopt this mapping. +## Protect the Endpoints {#endpoint-protection} -For a task containing an `items` array ({{access-request-response}}), each approved item MUST include a per-item `result` that is independently enforceable according to its own `result.mode`. +The Access Request Endpoint and Task Status Endpoint are protected APIs. Support for OAuth 2.0 {{RFC6749}} is RECOMMENDED. When OAuth 2.0 bearer tokens are used, the endpoints MUST follow {{RFC6750}}. The Cancellation endpoint ({{cancellation}}) is similarly protected; its authorization rules are defined in that section. -When `result.mode` is `reevaluate`, the result MUST include an `approval` member. The `approval` object identifies the approval that completed the Access Request task and has the following members: +The Access Request Service MUST authenticate the PEP or caller before accepting a submission or returning task status. The service MUST verify that the caller is authorized to submit or view the request for the supplied Subject, Resource, and Action. -* `id`: REQUIRED. String. Stable, opaque, and unguessable identifier of the approval. The value MUST contain sufficient entropy to prevent practical guessing and MUST NOT encode semantics that a PEP is expected to parse. -* `approved_at`: OPTIONAL. {{RFC3339}} timestamp indicating when the approval completed. -* `approved_until`: REQUIRED. {{RFC3339}} timestamp indicating the latest time through which the approval remains valid. The PEP MUST NOT use the approval for re-evaluation after this timestamp. +When authenticating a submission, the Access Request Service MUST authenticate the PEP using the deployment's chosen mechanism (typically an OAuth 2.0 bearer token, mutual TLS certificate, or signed assertion). -The `approval` object MAY additionally include a `state` member. `state` is an opaque JSON value populated by the Access Request Service or PDP, carrying proof or verifier state the PDP needs at re-evaluation time (for example, a signed reference, an extended lookup token, or deployment-specific state). The PEP MUST preserve the JSON value exactly and MUST NOT modify or interpret the contents of `approval.state`. +## Process a Submission {#submission-processing} -The `evaluation_id` of the original denied evaluation is denial-binding material for the Access Request submission; it is not the authorization handle used during re-evaluation. During re-evaluation, the chain back to the approved Access Request and original denial is represented by the `approval` object. The PDP MUST be able to resolve or verify `approval.id`, `approval.state`, or both, and bind the approval to the Access Request task, the original denied evaluation when recorded, the approved Subject, Resource, Action, relevant Context, approval scope, and approval expiry. When both `approval.id` and an integrity-protected `approval.state` are present and `approval.state` carries its own approval identifier, the PDP MUST verify that the two identifiers match, and MUST reject the re-evaluation on mismatch. +An Access Request Service implementing this profile: -The PDP MUST NOT authorize a re-evaluation solely because the request contains a known `approval.id`. The PDP MUST verify that the approval reference presented in `context.approval` is applicable to the authenticated caller or requester, current Subject, Resource, Action, relevant Context, approval scope, and approval expiry. A swapped, replayed, expired, or otherwise non-applicable approval reference MUST be ignored or rejected, and the PDP MUST evaluate the request as not approved by that reference. +* MUST validate that the submission is based on a requestable denial, rejecting a submission that is not with `urn:openid:authzen:access-request:error:not_requestable`. +* MUST verify the denial-binding material for every requested item, applying the following rules: + * When `denial.binding_token` is absent, the service MUST apply the `evaluation_id` verification path defined in {{shared-state-deployments}}. + * The service MUST reject submissions received after the verified `denial.expires_at` with `urn:openid:authzen:access-request:error:expired_denial`, after applying any clock-skew tolerance it has configured (see {{time-and-clock-skew}}). + * The service MUST reject submissions whose binding material cannot be verified, or whose claims do not bind to the submitted denial, with `urn:openid:authzen:access-request:error:invalid_denial_binding`. +* MUST bind the task to the submitted Subject, Resource, Action, Context, denial, requester, and client. +* MUST return an opaque Task Handle for accepted requests. +* SHOULD support idempotent request submission using the `Idempotency-Key` header ({{submission-idempotency}}). -An approval reference has two deployment patterns: +The submitted `denial` object for each requested item MUST include either `denial.binding_token` or `denial.evaluation_id`. The Access Request Service MUST reject a submission that lacks verifiable denial-binding material with `urn:openid:authzen:access-request:error:invalid_denial_binding`. -* Lookup: the PDP resolves `approval.id` in trusted server-side state. -* Bound reference: the PDP verifies `approval.state`, which carries integrity-protected proof or verifier state. +An Access Request whose denial binding does not cover the submitted Subject, Resource, Action, and authorization-relevant Context (for every item when `items` is present) MUST be rejected with `urn:openid:authzen:access-request:error:invalid_denial_binding`. -Deployments MAY use both patterns together. In all cases, the PDP MUST verify the approval against trusted state or integrity-protected binding material; neither `approval.id` nor `approval.state` is a bearer grant by itself. +The Access Request Service MUST be able to resolve or validate `denial.evaluation_id` before relying on it as denial-binding material. -When the PDP cannot resolve `approval.id` from trusted server-side state shared with, or delegated by, the Access Request Service, the Approval Result MUST include `approval.state` or another profile-defined PDP-verifiable artifact. An Access Request Service MUST NOT return a Re-evaluation Mode result that the PDP cannot verify without trusting PEP-supplied assertions. +The Access Request Service MUST NOT rely on `client.actor` or `client.source` ({{ACTOR}}) as authorization input unless the values are independently verified by the service. -When `approval.state` is carried by value as a JWS, the verifying PDP MUST be able to discover the signer's verification key. The JWS MUST carry an `iss` claim identifying the signer (the Access Request Service, or the PDP acting through it) and SHOULD carry a `kid` header. The verification key is published in the PDP's `jwks_uri` JWK Set ({{discovery}}), which holds the verification keys for every signed artifact this profile defines; the PDP selects the key by `iss` and `kid`. Because the Access Request Service is logically part of, trusted by, or delegated by the PDP, the deployment ensures the Access Request Service's approval-state signing keys are present in that JWK Set. A JWS `approval.state` MUST carry an `aud` (or equivalent intended-recipient) claim identifying the verifying PDP, which the PDP MUST verify, so the value cannot be replayed to a different PDP that shares the signer's key. A PDP that cannot resolve the signer's key, or resolves it to a key not trusted for the claimed `iss`, MUST reject the `approval.state`. This `jwks_uri` is symmetric across both directions: the Access Request Service verifies PDP-signed denial binding from it, and the PDP verifies Access-Request-Service-signed approval state from it. +{{ACTOR}} defines actor-chain verification; {{verifying-denial-binding}} defines the denial-binding verification procedure. -The approval record or verifiable binding material MUST contain, or allow the PDP to determine, at least the approval identifier, Access Request task identifier, original denied evaluation identifier when available, approved Subject, approved Resource and Action or approval scope, requester and client binding, approval status, `approved_at` when available, `approved_until`, and any revocation or cancellation state. +The `task.id` value MUST contain sufficient entropy to prevent practical guessing and MUST NOT encode semantics that a PEP is expected to parse. -The PEP MUST include the `approval` object unchanged at `context.approval` inside the AuthZEN Authorization API re-evaluation request. The PDP receives the same `approval` shape it produced (id, timestamps, and any `state`) and uses it to identify and verify the approval. +When the Access Request Service is able to resolve the request synchronously (for example, when policy auto-approves and provisioning completes within the request), the Access Request Service SHOULD return `201 Created` with `task.status` already set to a terminal value and a populated `result` member ({{completion-semantics}}). -The PDP MUST evaluate the new request using current policy and the approval reference. The PDP MAY still deny access if policy, subject, resource, action, context, approval lifetime, or risk state no longer permits access. +### Denial Binding by Reference {#shared-state-deployments} -When the PDP denies a re-evaluation that presented an `approval` reference, it SHOULD tell the PEP what to do next so the PEP reacts correctly instead of blindly retrying. The PDP conveys this with the following Decision Context members: +The Access Request Service resolves `evaluation_id` against state shared with, or delegated by, the PDP, within the binding window defined in {{denial-issuance}}. This form applies only to same-service or shared-state deployments: a stateless PDP retains no decision state to fetch. -* `next_action`: RECOMMENDED. String. The action the PEP should take, and the durable interoperability surface. One of: - * `request`: submit a new Access Request. The PDP MUST also include a fresh `context.access_request` ({{requestable-denial-context}}) so the PEP has a valid requestable-denial signal for the new submission. - * `retry`: re-evaluate the same request after a delay; the denial is expected to be transient. - * `none`: do not retry or re-request; the denial is terminal for this approval. +When `denial.binding_token` is absent, the Access Request Service MUST resolve or validate `denial.evaluation_id`, retrieve the Subject, Resource, Action, authorization-relevant Context, and `expires_at` recorded for that evaluation in shared state, verify the Subject, Resource, Action, and Context match the submission using the structural comparison defined in {{structural-comparison}} (rejecting a mismatch with `urn:openid:authzen:access-request:error:invalid_denial_binding`), and enforce freshness against the recorded `expires_at` rather than the PEP-echoed `denial.expires_at`. - A PEP MUST drive its behavior from `next_action` when the value is recognized. A PEP that receives `next_action: "request"` without `context.access_request` MUST NOT submit a new Access Request and treats the denial as `none`. A PEP that receives no `next_action`, or an unrecognized value, falls back first to the default next action for a recognized `reason`, then to the requestable-denial signal. When the fallback action is `request`, the PEP treats the denial as `request` only when `context.access_request` is present; otherwise it treats the denial as `none`. -* `retry_after`: RECOMMENDED when `next_action` is `retry`. Integer. Number of seconds the PEP waits before re-evaluating the same request. This has the same value semantics as the delta-seconds form of the HTTP `Retry-After` field (Section 10.2.3 of {{RFC9110}}), but is carried in the Decision Context because the Access Evaluation response itself is a successful protocol response. A PEP that receives `next_action: "retry"` without `retry_after` SHOULD apply bounded exponential backoff with jitter, starting at several seconds and growing to no more than one minute between attempts, and MUST stop retrying once the approval expires. -* `reason`: OPTIONAL. String. A machine-readable reason code for UX and audit. This profile defines the following well-known re-evaluation denial reason codes with their default `next_action`: - * `approval_expired` (`request`): the approval is no longer valid because `approved_until` has passed or it was revoked, cancelled, or superseded. - * `out_of_scope` (`request`): the approval is valid but the current evaluation falls outside its approval scope. - * `grant_pending` (`retry`): the approval is valid and in scope, but the backing entitlement, role, or grant is not yet present (for example, provisioning has not completed). - * `policy_denied` (`none`): the approval is valid and in scope, but current policy, subject status, or risk state denies the request; re-requesting will not help. - * `approval_unverifiable` (`none`): the presented `approval.id` or `approval.state` could not be resolved or verified, or its binding did not match. +### When Binding Artifacts Are Used {#ars-artifact-conformance} - Implementations MAY register additional reason codes (for example, a more specific `approval_revoked`); a PEP uses a recognized `reason`'s registered default only when `next_action` is absent or unrecognized. A PEP treats an unrecognized `reason` as informational and relies on `next_action` or the fallback rule above. These codes are registered in the AuthZEN Access Request Re-evaluation Denial Reason registry ({{iana-reeval-reasons}}). +The Access Request Service applies the artifact-specific verification rules in {{binding-artifacts}} to every requested item: -The PDP MUST check current approval status during re-evaluation, including whether the approval has been revoked, cancelled, superseded, or otherwise invalidated before `approved_until`. The `approved_until` timestamp is a PEP-side maximum reuse and enforcement bound; it does not prevent the PDP from denying earlier because of revocation, cancellation, policy change, risk change, or other current state. +* When `denial.binding_token` is present, the service MUST verify its integrity. When the value is a JWS, the service MUST verify the signature using a key resolved from the JWK Set advertised at the PDP's `jwks_uri` ({{binding-keys}}); JWS `kid` headers are matched against JWK `kid` parameters. -When the re-evaluation response indicates an approval expiry (typically as `context.approval.approved_until`), the PEP MUST NOT enforce access past that timestamp. PEPs that issue downstream credentials on the basis of the approved evaluation (for example, an OAuth Authorization Server issuing access tokens) MUST bound the lifetime of those credentials by the earlier of the approval expiry in the Approval Result and any approval expiry returned by the PDP during re-evaluation. +### Idempotency and Retries {#submission-idempotency} -When the original submission carried an `items` array, the PEP re-evaluates each approved item separately, including that item's `result.approval` at `context.approval` in the item's re-evaluation request as described above. This profile does not define an aggregate re-evaluation that covers multiple items in one AuthZEN Authorization API call. +The Access Request Service SHOULD treat a repeated submission with the same `Idempotency-Key`, the same authenticated requester, and an equivalent submission body as the same request, returning the same Task Handle while the original request remains available. A submission with the same `Idempotency-Key` and authenticated requester but a materially different submission body MUST be rejected with `urn:openid:authzen:access-request:error:duplicate_request`. -Approval results in this mode typically cover a class of future evaluations rather than a single submission. An approval that grants the requester an entitlement, role, scope, or other persistent state causes subsequent AuthZEN Authorization API evaluations matching that state to succeed without further Access Requests. Deployments serving high-volume callers, such as autonomous agents that discover and request many fine-grained permissions over time, rely on this property: a single broad-scope approval (for example, one that grants access to a class of resources for a defined duration) reduces the number of denial-and-approval cycles by orders of magnitude. +Bodies are equivalent when identical under the Access Request Service's deterministic comparison; otherwise they are materially different. For example, stable JSON canonicalization can ignore insignificant whitespace and object-member ordering; the `Idempotency-Key` header is outside the body. The same service records keys and evaluates retries, so the comparison need not be interoperable across implementations. -An Approval Result is associated with an approval scope: a description of the class of future Access Evaluations for which the approval may be considered. This specification does not define a general approval-scope matching language. It defines one portable baseline and leaves broader matching to deployments and downstream profiles: +The Access Request Service SHOULD retain Idempotency-Key state at least until `task.expires_at` and SHOULD continue to retain it for at least 24 hours after the task reaches a terminal status. This window lets retries from delayed PEP restarts find the original task rather than spawning a duplicate. -* Exact-match baseline (interoperable). The default approval scope is the original denied Subject, Resource, Action, and authorization-relevant Context bound to the Access Request. An evaluation is within this scope when its Subject, Resource, Action, and authorization-relevant Context are equal, member by member, to the bound values, using the same structural comparison and authorization-relevant Context set as denial binding. Subject, Resource, and Action comparison includes the full AuthZEN Authorization API objects, including any `properties` members present in the bound values, except that `subject.properties.act` is excluded because the PEP MAY normalize the actor to `client.actor` ({{delegation}}). This baseline is engine-neutral, and two independently implemented PDP and Access Request Service pairs MUST interoperate on it. In the bound-reference topology, where the verifying PDP does not share recorded state with the Access Request Service, the verifiable approval material (for example, a `binding_context_members`-equivalent claim in `approval.state`) MUST convey the authorization-relevant Context member set so the PDP applies the same set. -* Broadened scope (deployment-defined). Broader approvals (a class of resources, a role or entitlement, a time-bounded tool class) are where the broad-approval benefit lives, but their matching is not portable across policy engines. Broadened-scope representation and matching are deployment-specific or defined by downstream profiles. This profile deliberately does not define context-constraint matching. +After the retention window elapses, the Access Request Service MAY reclaim the Idempotency-Key; a submission presenting a previously seen Idempotency-Key whose state has been reclaimed is processed as a new submission. -Approval workflow policy at the Access Request Service determines how broad an approval grants; this profile does not constrain that policy beyond the integrity, expiry, and audit requirements stated elsewhere. +#### Idempotency Key Abuse {#idempotency-key-abuse} -Unless the Access Request Service or PDP records a broader or narrower approval scope, the default approval scope is the original denied Subject, Resource, Action, and relevant Context bound to the Access Request. For a bundled Access Request, the default approval scope for each approved item is that item's Subject, Resource, Action, and relevant Context. This default scope is not serialized in the Approval Result unless a profile or deployment defines a representation for it. +Implementations SHOULD scope idempotency keys to the authenticated caller and avoid storing them longer than necessary. -The PDP MUST only consider an Approval Result applicable when the current evaluation request is within the approval scope recorded for that Approval Result. +## Run the Task {#ars-task} -A PEP MUST NOT treat an Approval Result as authorizing any future Access Evaluation solely on the basis that the Access Request was approved. A PEP MAY include the Approval Result in a subsequent Access Evaluation (by placing the `approval` object at `context.approval` as described above), but the PDP remains responsible for determining whether the Approval Result applies under current policy. A PEP MAY cache or retain an Approval Result, but MUST NOT independently infer that a future request is covered by that approval unless directed by the PDP or by a profile-defined mechanism. +An Access Request Service implementing this profile: -The following non-normative example carries an integrity-protected `approval.state` (here a compact JWS signed by the Access Request Service), which the PDP verifies at re-evaluation. This is the portable form: it works whether or not the PDP and Access Request Service share state, and a PDP coding to it has verifiable binding material rather than a bare identifier. A deployment in which the PDP and Access Request Service share trusted state MAY instead omit `approval.state` and have the PDP resolve `approval.id` by server-side lookup. +* MUST expire Access Requests and approvals according to local policy. +* MUST NOT return `approved` unless the configured approval workflow has completed successfully. +* MUST evaluate approver eligibility, including self-approval, delegation, separation-of-duties, and conflict-of-interest policy, before treating an approval workflow as successfully completed. +* MUST retain sufficient audit records to reconstruct the request, approval, denial, and completion result. -Non-normative re-evaluation request: +For a completed task, the response: -~~~ json -{ - "subject": { - "type": "user", - "id": "alice@example.com" - }, - "resource": { - "type": "document", - "id": "q4-plan" - }, - "action": { - "name": "can_read" - }, - "context": { - "approval": { - "id": "apr_01HX4Y8E2NE3Y2X7P0K4JE6WVH", - "approved_at": "2026-04-30T20:42:00Z", - "approved_until": "2026-05-01T00:42:00Z", - "state": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImFycy0xIn0.eyJhcHByb3ZhbF9pZCI6ImFwcl8wMUhYNFk4RTJORTNZMlg3UDBLNEpFNldWSCJ9.c2lnbmF0dXJl" - }, - "time": "2026-04-30T20:43:00Z" - } -} -~~~ +* When `task.status` is `approved` and the task does not contain an `items` array, the response MUST include a top-level `result` object. +* For any other terminal status, the response MAY include a `result` object for diagnostic or workflow information. +* When present, the `result` object MUST use one of the completion forms defined in {{completion-semantics}}. -Non-normative re-evaluation response: +A task remains retrievable from the Task Status Endpoint after it has reached a terminal status, until `task.expires_at` is reached or the Access Request Service removes it according to local retention policy. After expiry or removal, the Task Status Endpoint MUST return `urn:openid:authzen:access-request:error:task_expired` or `urn:openid:authzen:access-request:error:unknown_task` as appropriate. -~~~ json -{ - "decision": true, - "context": { - "approval": { - "id": "apr_01HX4Y8E2NE3Y2X7P0K4JE6WVH", - "approved_until": "2026-05-01T00:42:00Z" - } - } -} -~~~ +The `approval.id` value ({{approval-result}}) MUST contain sufficient entropy to prevent practical guessing and MUST NOT encode semantics that a PEP is expected to parse. -# Callback Completion {#callback-completion} +The Access Request Service SHOULD retain the original `evaluation_id` in the approval record so the sequence from denied evaluation through Access Request, approval, and re-evaluation can be reconstructed for audit. -A PEP MAY request callback notification by including a `callback` object in the Access Request submission. +Implementations SHOULD apply privacy controls before populating `progress.awaiting`; see {{privacy-considerations}}. -The `callback` object has the following members: +### Status Mapping Obligation {#status-mapping-obligation} -`endpoint`: -: REQUIRED. HTTPS URI to which the Access Request Service sends completion notifications. The Access Request Service MUST validate that the endpoint is authorized for the authenticated PEP, either by matching a pre-registered callback URI or by applying an explicit deployment allowlist. The Access Request Service MUST reject callback endpoints that resolve to loopback, link-local, private-use, or otherwise internal network addresses unless the deployment has explicitly allowed that destination. In-cluster or same-trust-domain deployments, where the PEP's callback endpoint is legitimately an internal address, permit those specific destinations through this explicit allowlist rather than by disabling the check; the secure default of blocking internal destinations protects internet-facing Access Request Services from server-side request forgery. +Implementations SHOULD document the mapping they apply from their backend states to the canonical statuses so that PEP behavior remains predictable across upgrades and operational changes. Typical mappings are illustrated in {{status-mapping}}. -`state`: -: OPTIONAL. Opaque value supplied by the PEP and returned unmodified in the callback. +Implementations that define additional status values ({{task-status}}) extend the state machine. Such extensions SHOULD specify the transitions into and out of the new state and document them alongside the value definition. -`events`: -: OPTIONAL. Array of event names requested by the PEP. Defined event names are `approved`, `denied`, `expired`, `cancelled`, `failed`, and `partial`. +### Availability {#availability} -Callback notifications MUST contain a `task` member and MAY contain a `result` member. When present, the `result` object MUST use one of the completion forms defined in {{completion-semantics}}. A callback whose `task.status` is `approved` but that does not contain an enforceable `result` is only a notification; the PEP MUST retrieve the Task Status Endpoint response before enforcing access. +Access Request Services SHOULD apply rate limits and abuse detection to request submission and polling endpoints. -The Access Request Service MUST authenticate to the callback endpoint using a mechanism agreed between the PEP and Access Request Service. This specification does not mandate a single callback authentication mechanism, but implementations SHOULD use one of the following: an OAuth 2.0 bearer token {{RFC6750}} issued to the Access Request Service, mutual TLS, or an HMAC signature over the request body using a pre-shared key. Unauthenticated callbacks MUST NOT be accepted. +### Task Handle Authorization {#authorization-and-authentication} -Callback delivery is a notification optimization. The Task Status Endpoint remains authoritative unless the callback contains an enforceable completion result under {{completion-semantics}}. +Authorization for Task Handle interactions, such as status retrieval and cancellation, is bound to the original Subject, Resource, Action, task, and requested operation rather than to a specific access token, session, or PEP instance. -Implementations MAY satisfy completion notification through deployment-level event subscriptions (for example, organization-scoped webhooks or event-streaming bindings defined by companion specifications) rather than per-task callbacks. When a deployment relies on such a subscription, the PEP MAY omit the `callback` member from the Access Request submission. Deployment-level event subscriptions deliver the same Task Handle and lifecycle information to subscribed receivers; they are a notification channel and MUST NOT be treated as enforcement unless paired with a separate enforceable result. +The Access Request Service MUST authorize each Task Handle operation independently. Authorization to retrieve task status does not imply authorization to cancel the task, view approver details, or retrieve an enforceable result. -Non-normative callback example. This callback is notification-only because it does not include a `result`; the PEP polls the Task Status Endpoint before enforcing access. +A task status response MUST NOT disclose approval details, approver identities, policy identifiers, or resource metadata to a caller that is not authorized to receive them. -~~~ http -POST /callbacks/access-requests HTTP/1.1 -Host: pep.example.com -Authorization: Bearer mF_9.B5f-4.1JqM -Content-Type: application/json +#### Task Handle Leakage {#task-handle-leakage} -{ - "state": "b3Blbi1kb2N1bWVudC1mbG93", - "task": { - "id": "arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4", - "status": "approved", - "status_endpoint": "https://pdp.example.com/access/v1/requests/arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4" - } -} -~~~ +Task handles MUST be opaque, unguessable, and protected by authentication and authorization checks. A leaked task handle MUST NOT be sufficient to retrieve task status without caller authorization. -# Error Responses {#error-responses} +## Policy and Approver Hygiene {#overbroad-approval} -HTTP error responses from the Access Request Endpoint and Task Status Endpoint MUST use `application/problem+json` as defined by {{RFC9457}} when returning one of the problem types defined by this specification. The problem type URI MUST appear in the `type` member. +This profile does not define an approval policy language. Implementations MUST NOT treat the `template`, `requested_access`, or `display` fields as sufficient authorization policy. Actual approval scope and enforcement semantics are determined by the PDP and Access Request Service. -The following problem types are defined: +The `requested_access.emergency` member ({{submission-request-body}}) is a request signal, not an authorization override. Implementations that support emergency or break-glass access SHOULD require a business justification, apply the shortest practical approval or access lifetime, notify appropriate owners or security personnel, and require post-use review. Emergency requests and approvals SHOULD be retained and auditable according to the deployment's security and compliance policy. -`urn:openid:authzen:access-request:error:not_requestable`: -: HTTP `400 Bad Request`. The submitted denial is not requestable. +### Approver Eligibility and Separation of Duties {#approver-eligibility} -`urn:openid:authzen:access-request:error:expired_denial`: -: HTTP `410 Gone`. The requestable denial has expired: the freshness deadline (the earlier of `denial.expires_at` and any `binding_token` `exp`) has passed. +Access Request Services MUST evaluate approver eligibility before returning `approved`, including self-approval restrictions, delegated approver authority, separation-of-duties constraints, ownership rules, and conflict-of-interest policy. A workflow step completed by an ineligible approver MUST NOT be treated as successful approval unless local policy explicitly allows that exception and records it for audit. -`urn:openid:authzen:access-request:error:invalid_denial_binding`: -: HTTP `400 Bad Request`. The submitted Access Request cannot be bound to the denied AuthZEN Decision. +# Binding Artifacts {#binding-artifacts} -`urn:openid:authzen:access-request:error:duplicate_request`: -: HTTP `409 Conflict`. The `Idempotency-Key` was reused by the same requester with a submission body that is not equivalent to the original request (see {{access-request-submission}}). +This section defines how the PDP and the Access Request Service issue and verify binding artifacts at the denial and approval boundaries; a PEP carries the artifacts unchanged ({{pep-construct}}, {{approval-reevaluation-request}}) and needs nothing further from this section. The Interoperability Baseline requires support for the respective JWS verification paths in every deployment; shared-state deployments need not exchange binding artifacts, but their Access Request Services and PDPs still support those paths. Whenever `binding_token` or `approval.state` is used, its applicable processing rules apply, including any conditions on its format. Rules in this section that are stated for either binding form, or for the Approval Result the Access Request Service returns, apply in every deployment. -`urn:openid:authzen:access-request:error:unknown_task`: -: HTTP `404 Not Found`. The task handle is unknown or unavailable to the caller. +An independent Access Request Service needs `binding_token` to verify the denial. A PDP without trusted access to the approval record needs `approval.state` or another profile-defined PDP-verifiable artifact ({{approval-state}}). The `state` member can carry proof or verifier state; its JWS rules apply when it is carried by value as a JWS. -`urn:openid:authzen:access-request:error:task_expired`: -: HTTP `410 Gone`. The task handle has expired. +## Interoperability Baseline {#interoperability-baseline} -`urn:openid:authzen:access-request:error:invalid_task_state`: -: HTTP `409 Conflict`. The requested operation cannot be performed in the current task state (for example, cancellation of a task that has already reached a terminal status). +For cross-vendor interoperability, an Access Request Service MUST support verifying a `binding_token` presented as a JWS in compact serialization, and a PDP MUST support verifying an `approval.state` presented as a JWS in compact serialization ({{approval-state}}). -Non-normative example: +Other integrity-protected formats MAY be used when both the issuer and the verifier support them. -~~~ http -HTTP/1.1 400 Bad Request -Content-Type: application/problem+json +## Keys and Metadata {#binding-keys} + +A PDP that issues or verifies signed values for use under this profile (for example, a JWS-signed `binding_token` or a JWS `approval.state`, defined in {{approval-state}}) MUST publish a `jwks_uri` in PDP metadata. + +The value is an HTTPS URI of a JWK Set {{RFC7517}} document containing the verification keys for the signed artifacts this profile defines: PDP-issued `binding_token` values and `approval.state` values signed by the PDP's Access Request Service. Keys are distinguished by their `kid` and by the JWS `iss`. + +Each JWK in the set SHOULD include a `kid` parameter so JWS signatures issued with a `kid` header can be resolved to the corresponding verification key, and SHOULD include a `use` parameter distinguishing signing keys (`use: "sig"`) from any other keys advertised. + +Verifiers cache the JWK Set per HTTP cache headers and refresh it on key-rotation events. An unrecognized `kid` SHOULD cause the verifier to refresh the JWK Set before rejecting the input. + +Non-normative metadata example: +~~~ json { - "type": "urn:openid:authzen:access-request:error:not_requestable", - "title": "Access is not requestable", - "status": 400, - "detail": "The denied decision did not contain a context.access_request object." + "policy_decision_point": "https://pdp.example.com", + "access_evaluation_endpoint": "https://pdp.example.com/access/v1/evaluation", + "access_evaluations_endpoint": "https://pdp.example.com/access/v1/evaluations", + "access_request_endpoint": "https://pdp.example.com/access/v1/requests", + "jwks_uri": "https://pdp.example.com/access/v1/jwks", + "capabilities": [ + "urn:openid:authzen:capability:access-request" + ] } ~~~ -# Extensibility and Profiles {#extensibility} +## Denial Binding Artifacts {#denial-binding} -This specification defines a base wire format. Several of its objects are intentionally extensible so that profiles, deployments, and implementations can adapt the model to specific upstream protocols, governance platforms, and request user interfaces without breaking interoperability. +Denial binding uses `binding_token` by value or `evaluation_id` by reference. -## Extension Points +**`binding_token` (by value).** An integrity-protected token that protects or constrains the requestable-denial expiry. The PDP signs it and retains no state. This form works in any topology, and is REQUIRED when the Access Request Service does not share state with the PDP (an independent Access Request Service). -Additional members beyond those defined in this document MAY appear only at the following locations, and those members MUST follow the naming rules in {{extension-naming}}. No other object members may be extended without a revision of this specification or a profile that explicitly redefines them. +For that independent topology the `binding_token` MUST be self-contained: it carries the denied Subject, Resource, Action, and authorization-relevant Context by value (inline or as a `binding_hash`) and the Access Request Service verifies it offline against the PDP's published key, per {{binding-token-integrity}}. A `binding_token` that only references shared state, for example one carrying `evaluation_id` alone, does not satisfy the independent-Access-Request-Service requirement. -* `context.access_request.display`: user-interface hints in a requestable denial. -* AuthZEN Decision Context members defined by this profile. -* `context` in an Access Request submission: augments the AuthZEN Context. -* `requested_access` in an Access Request submission. -* `client`, `client.actor`, and `client.source` in an Access Request submission. -* A Catalogs Document and a Catalog Reference object within a Catalogs Document. -* A Catalog Item within a Catalog Response. -* `task.display`: user-interface hints attached to a Task Handle. -* `task.links`: link relations to related URLs. -* `result` and the additions defined under each `result.mode`. -* `approval.state` in a Re-evaluation Mode result: opaque profile-specific or deployment-specific verifier state carried through the PEP to the PDP at re-evaluation time. +A PDP MAY emit both forms. When a `binding_token` is present it is the authoritative denial binding, and any accompanying `evaluation_id` serves only as a correlation and audit identifier rather than a second binding form; the Access Request Service resolves `evaluation_id` as a binding form only when no `binding_token` is present. This is why a PDP MAY follow the conformance recommendation to return a stable `context.evaluation_id` ({{evaluation-identifier}}) even when it also emits a `binding_token`. -This specification also defines extensibility for enumerated values: +The following protection rules apply to `binding_token`: -* New values for `task.status` ({{task-status}}). -* New values for `result.mode` ({{completion-semantics}}). -* New problem types for {{RFC9457}}-style error responses ({{error-responses}}). +* When present, the value MUST be integrity protected in a way the Access Request Service can verify, and SHOULD be a JSON Web Signature (JWS) {{RFC7515}} in compact serialization, signed by the PDP, with a payload (such as a JWT {{RFC7519}}) that the Access Request Service can verify and bind to the original denied evaluation. +* JSON Web Encryption (JWE) {{RFC7516}} MAY be used in addition to integrity protection when the payload contains information that must not be visible to the PEP, for example by encrypting a signed payload. -This specification does not create registries for these enumerated values. Specifications that define new values for `task.status`, `result.mode`, or problem types SHOULD define stable names or URIs and processing rules for those values. Short, unqualified names for `result.mode` are reserved for values defined by this base specification or by a future registry; profile-defined `result.mode` values SHOULD use absolute URIs unless such a registry exists. +### Denial Binding Claims {#binding-token-integrity} -## Naming Extensions {#extension-naming} +The PDP issues `binding_token` as proof of the denied evaluation. The PEP carries it unchanged to the Access Request Service, which verifies the binding and freshness ({{verifying-denial-binding}}). -A member name or value added at an extension point MUST be one of the following: +PDPs MUST integrity-protect `binding_token` using a mechanism the Access Request Service can verify and SHOULD issue it as a JWS so the Access Request Service can prove the value was produced by the PDP and bound to the original denied evaluation. -1. A name registered in the AuthZEN Access Request Member Names registry ({{iana-member-names}}). Registry-eligible names are short, lowercase, snake_case identifiers carrying semantics that are useful across multiple implementations. -2. An absolute URI (HTTPS or URN) when the member is profile-specific and not appropriate for the registry. Profiles SHOULD use a stable URI under the profile's change controller. -3. A reverse-DNS-prefixed identifier (for example, `vendor.example.com/foo`) when the member is private to a single deployment and not intended for cross-implementation use. +When the payload contains information that must not be visible to the PEP, the PDP MAY use JWE in addition to integrity protection, for example by encrypting a signed payload. -The contents of `approval.state` are opaque to this specification and are not subject to the member naming requirements above unless a profile or deployment explicitly defines structure within `approval.state`. +This profile does not mandate a specific JWS payload; the contents are deployment-specific. Implementations that issue `binding_token` as a JWT SHOULD include the claims described in {{denial-jwt-claims}} and {{denial-request-binding}} to provide sound token hygiene and confused-deputy protection. -## Forward Compatibility +#### JWT Claim Reference {#denial-jwt-claims} -An implementation receiving a member or value it does not recognize at an extension point MUST ignore it and MUST NOT fail processing on the basis of the unrecognized name. This default does not override fail-safe rules defined elsewhere in this profile, such as the PEP rule in {{pep-processing-rules}} that treats unknown `result.mode` values as not approved rather than as ignorable. An implementation MAY surface unrecognized members in audit records or pass them through unchanged when echoing wire content (for example, in callbacks). +* `aud`: REQUIRED. Access Request Service identifier, or an array of identifiers including the Access Request Service. An array supports multiple verifiers of the same JWT; audience validation prevents replay to an unintended service. The Access Request Service MUST reject a `binding_token` JWT that lacks `aud` or whose `aud` does not include the Access Request Service's identifier. +* `iss`: PDP identifier. Lets the Access Request Service select the correct verification key from the PDP's JWK Set ({{binding-keys}}). +* `iat`, `exp`: issued-at and expiry. Expiry SHOULD be short (typically minutes, aligned with the requestable-denial hint lifetime). +* `jti`: unique token identifier. The Access Request Service SHOULD track recently-seen `jti` values until the token's `exp` to detect replay of an otherwise valid token. +* `denial_expires_at`: the `context.access_request.expires_at` value from the requestable denial, unless the token's `exp` is no later than that value. This lets the Access Request Service verify the PEP-echoed `denial.expires_at` value or enforce the token expiry as an equal-or-stricter freshness deadline. +* `evaluation_id`: the PDP's identifier for the evaluation, when present in `context.evaluation_id` ({{evaluation-identifier}}). -## Profiles +#### Binding the Denied Request {#denial-request-binding} -A profile of this specification is a separate document that defines a coherent set of extensions for a particular use case. Examples include a profile binding this specification to OAuth 2.0 token requests, a profile carrying Rich Authorization Requests {{?RFC9396}}, or a profile describing integration with a specific governance platform. +The `binding_context_members` claim is the array of `context` member names that constitute the authorization-relevant Context for this evaluation (see the Terminology definition of Authorization-Relevant Context). It is present (and MAY be an empty array) whenever any binding claim covers context; the Access Request Service uses exactly this integrity-protected set when comparing or hashing the authorization-relevant Context, and binds only Subject, Resource, and Action when it is absent. -A profile SHOULD: +Binding claims identify the original denied evaluation using either of the following representations. For interoperability across independently implemented PDPs and Access Request Services, the inline form is RECOMMENDED, because it is compared structurally and requires no agreed byte canonicalization. -* Identify itself with a stable URI. -* Specify the extension points it populates and the member names or enumerated values it introduces. -* Register registry-eligible member names in the AuthZEN Access Request Member Names registry ({{iana-member-names}}). -* Define semantics, validation rules, and any normative requirements for its members. -* Enumerate any constraints it places on members or behaviors defined by this base specification. +* Inline (RECOMMENDED): the Subject, Resource, Action, and authorization-relevant Context of the denied evaluation, which the Access Request Service compares structurally, member by member, against the submission, using the comparison rules in {{structural-comparison}}. +* Hashed: a `binding_hash` constructed as defined in {{denial-binding-hash}}. -This base specification does not enumerate profiles. Conformance to a profile is determined by the presence and processing of the profile's registered or namespaced members; this specification does not require declarative profile negotiation. +#### Hash Construction {#denial-binding-hash} -# PEP Processing Rules {#pep-processing-rules} +`binding_hash` is the base64url-encoded (without padding) SHA-256 digest of the {{RFC8785}} JSON Canonicalization Scheme (JCS) serialization of this JSON object. Angle-bracketed values are placeholders, not literal strings: -A PEP implementing this profile: +~~~ +{ + "subject": , + "resource": , + "action": , + "context": +} +~~~ -* MUST treat `decision: false` as a denial, even when the Decision Context contains an `access_request` object. -* MUST NOT submit an Access Request unless the denied Decision contains a `context.access_request` object. -* MUST use the `endpoint` from the denial context when present; otherwise it MUST use the `access_request_endpoint` from PDP metadata. -* MUST preserve the principal identity of the Subject, and MUST preserve the Resource, Action, and relevant Context of the denied evaluation when submitting the Access Request. When the original evaluation conveyed an actor identity in the Subject (for example, via `subject.properties.act`), the PEP MAY preserve the actor in the submission's `subject` or normalize it to `client.actor`; the actor identity itself MUST NOT be dropped. -* When the requestable denial includes `request_schema_url` or `request_catalogs_url`, MUST construct the augmentations to the submission's `context` and `requested_access` objects according to {{machine-readable-forms}} and {{catalog-references}}, or MUST NOT submit the Access Request if the required augmentations cannot be supplied. -* MUST include `denial.expires_at` from `context.access_request.expires_at`. -* MUST include `denial.evaluation_id` when `denial.binding_token` is absent, and SHOULD include it when the PDP returned an evaluation identifier. -* SHOULD include an idempotency key for Access Request submissions. -* MUST treat a Task Handle as opaque. -* MUST NOT infer approval from a task identifier, link, or display text. -* MUST treat unknown task status values as not approved. -* MUST enforce an approved result only according to {{completion-semantics}}. -* MUST treat unknown `result.mode` values as not approved. -* When using Re-evaluation Mode, MUST include the returned `approval` object unchanged at `context.approval` inside the AuthZEN Authorization API re-evaluation request. -* MUST re-evaluate access through the AuthZEN Access Evaluation API after approval, unless a profile-defined completion mode applies (for example, a profile binding to OAuth token issuance). -* MUST NOT treat an Approval Result as authorizing any future Access Evaluation solely on the basis that the Access Request task reached `approved`; applicability is determined by the PDP at each subsequent evaluation. +`` is the bound Subject with `subject.properties.act` removed (matching the exclusion in {{structural-comparison}}). `` is the set enumerated by `binding_context_members`, which the Access Request Service recomputes from the submission. -# PDP Processing Rules +Implementations that use the hashed form MUST use exactly this construction so that a PDP and an independently implemented Access Request Service compute identical digests. -A PDP implementing this profile: +The bulk construction is defined in {{BULK}}. -* MAY include `context.access_request` in a denied AuthZEN Decision when the denied access is eligible for approval. -* MUST NOT include `context.access_request` unless an Access Request Endpoint is available to process the request. -* SHOULD include a stable machine-readable reason code when returning a requestable denial. -* MUST include an expiration time for the requestable denial hint as `context.access_request.expires_at`. -* MAY include `form_url`, `request_schema_url`, and `request_catalogs_url` in the requestable denial when the Access Request requires additional submission fields beyond those produced by the original AuthZEN Authorization API evaluation. -* MUST include `request_schema_url` when including `request_catalogs_url`. -* MUST provide verifiable denial-binding material when returning `context.access_request`: an integrity-protected `context.access_request.binding_token`, or a stable `context.evaluation_id` the Access Request Service can resolve against state shared with, or delegated by, the PDP. When the Access Request Service is independent of the PDP, the PDP MUST provide the `binding_token` form ({{requestable-denial-context}}). -* SHOULD return a stable evaluation identifier as `context.evaluation_id` ({{evaluation-identifier}}) that the PEP can supply as `denial.evaluation_id` when submitting an Access Request. -* When including `context.access_request.binding_token`, MUST integrity-protect it using a mechanism the Access Request Service can verify and SHOULD issue it as a JWS in compact serialization. -* MUST validate approval references presented during re-evaluation. -* MUST only consider an Approval Result applicable when the current evaluation request is within the approval scope recorded for that Approval Result. -* MUST ensure that approval does not override policy conditions that remain mandatory at enforcement time, such as subject status, resource sensitivity, action constraints, environmental risk, and approval expiry. +### Verifying the Denial Binding {#verifying-denial-binding} -# Access Request Service Processing Rules +When `binding_token` is a JWS-signed JWT using the claims defined in {{binding-token-integrity}}, the Access Request Service, on receipt: -An Access Request Service implementing this profile: +1. parses the JWS header and resolves the verification key from the JWK Set at the PDP's `jwks_uri`; +2. verifies the signature, the `aud` claim, and the expiry; +3. checks `jti` against recently-seen tokens to detect replay; +4. compares the binding claims (inline or hashed) against the submission's Subject, Resource, Action, and authorization-relevant Context (or per-item for bulk submissions), rejecting a mismatch with `urn:openid:authzen:access-request:error:invalid_denial_binding`; +5. enforces freshness (the earlier of the token `exp` and `denial.expires_at`), rejecting a submission past that deadline with `urn:openid:authzen:access-request:error:expired_denial`. -* MUST authenticate and authorize the PEP before accepting Access Request submissions. -* MUST validate that the submission is based on a requestable denial, rejecting a submission that is not with `urn:openid:authzen:access-request:error:not_requestable`. -* MUST verify the denial-binding material for every requested item, applying the following rules: - * When `denial.binding_token` is present, the service MUST verify its integrity. When the value is a JWS, the service MUST verify the signature using a key resolved from the JWK Set advertised at the PDP's `jwks_uri` ({{discovery}}); JWS `kid` headers are matched against JWK `kid` parameters. - * When `denial.binding_token` is absent, the service MUST resolve or validate `denial.evaluation_id`, retrieve the Subject, Resource, Action, authorization-relevant Context, and `expires_at` recorded for that evaluation in shared state (the `evaluation_id` path is for shared-state deployments only; see {{requestable-denial-context}}), verify the Subject, Resource, Action, and Context match the submission using the structural comparison defined in {{binding-token-integrity}} (rejecting a mismatch with `urn:openid:authzen:access-request:error:invalid_denial_binding`), and enforce freshness against the recorded `expires_at` rather than the PEP-echoed `denial.expires_at`. - * The service MUST reject submissions received after the verified `denial.expires_at` with `urn:openid:authzen:access-request:error:expired_denial`, after applying any clock-skew tolerance it has configured (see {{impl-considerations}}). - * The service MUST reject submissions whose binding material cannot be verified, or whose claims do not bind to the submitted denial, with `urn:openid:authzen:access-request:error:invalid_denial_binding`. -* MUST bind the task to the submitted Subject, Resource, Action, Context, denial, requester, and client. -* MUST return an opaque Task Handle for accepted requests. -* SHOULD support idempotent request submission using the `Idempotency-Key` header. -* MUST expire Access Requests and approvals according to local policy. -* MUST NOT return `approved` unless the configured approval workflow has completed successfully. -* MUST evaluate approver eligibility, including self-approval, delegation, separation-of-duties, and conflict-of-interest policy, before treating an approval workflow as successfully completed. -* MUST retain sufficient audit records to reconstruct the request, approval, denial, and completion result. -* When operating Catalog Endpoints under {{catalog-references}}, MUST authorize callers and MUST return only Catalog Items the caller is permitted to see in the context of the original Subject, Resource, and Action. +The following rules govern the freshness deadline of a submission: -# Authorization and Authentication {#authorization-and-authentication} +* When the `binding_token` carries its own expiry (`exp`) and the submitted denial also carries `denial.expires_at`, the Access Request Service MUST enforce the earlier of the two as the freshness deadline for the submission. +* When `denial_expires_at` or equivalent protected binding material is present, the Access Request Service MUST verify that `denial.expires_at` matches the protected value before relying on it. +* When no protected denial-expiry value is present, the Access Request Service MUST rely on `exp` only if it is no later than the echoed `denial.expires_at`; otherwise the binding material is insufficient to prove the freshness window and the submission MUST be rejected with `urn:openid:authzen:access-request:error:invalid_denial_binding`. +* A submission whose freshness deadline has passed MUST be rejected with `urn:openid:authzen:access-request:error:expired_denial`. -The Access Request Endpoint and Task Status Endpoint are protected APIs. Support for OAuth 2.0 {{RFC6749}} is RECOMMENDED. When OAuth 2.0 bearer tokens are used, the endpoints MUST follow {{RFC6750}}. Catalog Endpoints ({{catalog-references}}) and the Cancellation endpoint ({{cancellation}}) are similarly protected; their authorization rules are defined in their respective sections. +### Denial Binding Alternatives {#denial-binding-alternatives} -The Access Request Service MUST authenticate the PEP or caller before accepting a submission or returning task status. The service MUST verify that the caller is authorized to submit or view the request for the supplied Subject, Resource, and Action. +PDPs MAY add deployment-specific claims (policy version, factors, risk score, tenant identifier) when the Access Request Service needs them for routing or audit. When such claims must remain opaque to the PEP, the PDP wraps the signed payload in JWE encrypted to the Access Request Service. -Authorization for Task Handle interactions, such as status retrieval and cancellation, is bound to the original Subject, Resource, Action, task, and requested operation rather than to a specific access token, session, or PEP instance. +When `binding_token` uses another integrity-protected format, the Access Request Service MUST perform equivalent verification for issuer authenticity, audience or intended recipient, expiry when present, replay resistance when provided by the format, and binding to the submitted Subject, Resource, Action, and relevant Context. -The Access Request Service MUST authorize each Task Handle operation independently. Authorization to retrieve task status does not imply authorization to cancel the task, view approver details, or retrieve an enforceable result. +A single signed JWT MAY simultaneously satisfy this profile's claim recommendations and the requirements of another profile or specification that uses the same JWT, provided the union of required claims is present and consistent. For example, the same JWT can appear as `context.access_request.binding_token` and as a profile-defined token elsewhere. Verifiers process the claims they understand without rejecting additional profile-specific claims. -Refresh of a calling identity's underlying token does not invalidate Task Handle access as long as the caller remains authorized for the bound task and operation. A different PEP instance or agent process that can authenticate as authorized for the bound task and operation MAY interact with the Task Handle. +## Approval State {#approval-state} -A task status response MUST NOT disclose approval details, approver identities, policy identifiers, or resource metadata to a caller that is not authorized to receive them. +When the PDP cannot resolve `approval.id` from trusted server-side state shared with, or delegated by, the Access Request Service, the Approval Result MUST include `approval.state` or another profile-defined PDP-verifiable artifact. An Access Request Service MUST NOT return a result under the `reevaluate` completion mode that the PDP cannot verify without trusting PEP-supplied assertions. -## Delegation and On-Behalf-Of {#delegation} +When `approval.state` is carried by value as a JWS: -A PEP submitting an Access Request frequently acts on behalf of one or more upstream principals. Common patterns include a SaaS application acting on behalf of an end user, an OAuth Authorization Server acting on behalf of a client and an end user, an agent runtime acting on behalf of an agent which acts on behalf of an end user, and a Security Token Service acting on behalf of an upstream caller. The protocol surface for these patterns is the AuthZEN Authorization API `subject` (carrying the principal) together with `client.actor` (carrying the immediate actor and, optionally, an `act` chain reaching back toward the Subject). +* The verifying PDP MUST be able to discover the signer's verification key. +* The JWS MUST carry an `iss` claim identifying the signer (the Access Request Service, or the PDP acting through it) and SHOULD carry a `kid` header. +* The deployment publishes the Access Request Service's approval-state verification keys in the PDP's `jwks_uri` JWK Set ({{binding-keys}}); the PDP selects the key by `iss` and `kid`. Publishing a signer's key there is the PDP's statement that it trusts that signer for approval-state verification; the JWK Set is a discovery surface and a trust declaration at once. +* A JWS `approval.state` MUST carry an `aud` (or equivalent intended-recipient) claim identifying the verifying PDP, which the PDP MUST verify, so the value cannot be replayed to a different PDP that shares the signer's key. +* A PDP that cannot resolve the signer's key, or resolves it to a key not trusted for the claimed `iss`, MUST reject the `approval.state`. -This profile does not define a new Subject shape for actor delegation. Implementations SHOULD follow the conventions defined in {{?I-D.mcguinness-oauth-actor-profile}}, which standardizes an `act` claim representing the immediate actor with required `sub` and `iss` members and a RECOMMENDED `sub_profile` member (taking values such as `ai_agent`, `service`, or `user`). Nested `act` objects represent multi-hop delegation chains. The canonical actor identifier is the (`iss`, `sub`) pair regardless of which carrier expresses it. +In the bound-reference pattern, the PDP verifies `approval.state`, carrying integrity-protected proof or verifier state. {{approval-reference-lookup}} defines the lookup alternative. -Under this profile: +In the bound-reference topology, where the verifying PDP does not share recorded state with the Access Request Service, the verifiable approval material (for example, a `binding_context_members`-equivalent claim in `approval.state`) MUST convey the authorization-relevant Context member set so the PDP applies the same set. -* The AuthZEN Authorization API `subject` carries the principal on whose behalf the operation is performed. -* `client.actor` (defined in {{access-request-submission}}) carries the immediate actor and MAY include a nested `act` claim that walks the delegation chain from the immediate actor outward toward the Subject. -* A PEP that captures actor information in the original AuthZEN Authorization API evaluation's `subject` (for example, via `subject.properties.act`) MAY preserve it in the submission's `subject` or normalize it to `client.actor`; the actor identity itself MUST NOT be dropped during reshaping. +The binding topology determines which PDP can verify the approval: an integrity-protected `approval.state` can be verified using the issuer's verification key, while lookup of `approval.id` requires access to the backing state. -The Access Request Service MUST authenticate the PEP using the deployment's chosen mechanism (typically an OAuth 2.0 bearer token, mutual TLS certificate, or signed assertion). When the submission claims an actor or actor chain in `client.actor`, the Access Request Service MUST verify that the authenticated caller's credential authorizes the entire claimed chain, not only the immediate actor. Mechanisms commonly used to provide such authorization include {{RFC8693}} OAuth 2.0 Token Exchange (where the access token names the Subject as the on-behalf-of party and the chain via `act` claims), signed assertions from a trusted issuer, or deployment-specific authentication policies. The Access Request Service MUST reject submissions whose claimed chain cannot be verified against the caller's credential or against trusted issuers identified in the deployment. +# Cancellation {#cancellation} -The Access Request Service MUST NOT treat `client.actor` content that has not been independently verified as authorization input; unverified content MAY be retained as audit metadata only. +Cancellation of a pending Access Request MAY be performed by the Access Request Service, the requester through a separate user interface, an approver, or the PEP using the cancellation endpoint defined in this section. -Approval routing at the Access Request Service MAY consider any identity in the chain (for example, routing approval to the principal's owner, the agent's deployment owner, or a delegated approver). This profile does not constrain routing policy; it only requires that the necessary identities be representable in the submission and verifiable by the Access Request Service before routing decisions are taken. +An Access Request Service MAY support PEP-initiated cancellation of a pending Access Request. When supported, the Task Handle MUST include a `links.cancel` member. When cancellation is not supported, `links.cancel` is omitted and a cancellation attempted against such a service returns `405 Method Not Allowed`. The PEP cancels by issuing an HTTP `POST` to `links.cancel`; implementations MAY also accept HTTP `DELETE` against `links.cancel` as an equivalent cancellation request. -Cross-implementation interoperability for delegated flows depends on adoption of a common actor convention. Deployments and profiles that depend on a specific actor convention SHOULD document the Subject shape, the actor convention used, and the credential format the Access Request Service accepts as proof of the chain. +The cancellation request body is an OPTIONAL JSON object with the following members: -# Privacy Considerations {#privacy-considerations} +`reason`: +: OPTIONAL. String. Stable, machine-readable reason code. -Access Requests may contain sensitive information, including user identifiers, resource identifiers, business justifications, approval chains, and policy reasons. Implementations SHOULD minimize the amount of information returned to the PEP and displayed to the end user. +`comment`: +: OPTIONAL. String. Human-readable cancellation note for audit. -The Access Request Service SHOULD separate end-user display reasons from administrator diagnostic reasons. A requestable denial response SHOULD avoid exposing internal policy identifiers unless the PEP is authorized for administrative diagnostics. +A successful cancellation returns `200 OK` and the updated `task` object whose `status` is `cancelled`. Cancellation of a task that has already reached a terminal status returns `409 Conflict` using the `urn:openid:authzen:access-request:error:invalid_task_state` problem type. -Approval records SHOULD be retained only as long as required by business, security, and compliance policy. +The Access Request Service MUST authenticate the PEP and MUST verify the PEP is authorized for the original Subject, Resource, Action, task, and cancellation operation. Authorization to submit the original request or to act for the Subject does not by itself authorize cancellation; the service MUST verify authorization for the bound Resource, Action, task, and operation. -# Security Considerations +PEPs that need to abandon an outstanding request without using this endpoint MAY stop polling and rely on `task.expires_at` and Access Request Service expiry to release resources. -## Decision and Binding Integrity +# Machine-Readable Forms {#machine-readable-forms} -### Denial Remains Denial +The requestable denial's `access_request` object has two members describing additional input the Access Request Service expects at submission: -The presence of `context.access_request` does not weaken the AuthZEN Authorization API decision. A PEP MUST NOT grant access based on a requestable denial. Access is permitted only after an approved completion result is enforced according to this profile. +`form_url`: +: OPTIONAL. HTTPS URI. URL of a form, hosted by the Access Request Service or another service trusted by the deployment, where the requester can supply additional information required for the Access Request. Suitable for PEPs that render the form for a human user. -### Confused Deputy and Request Substitution +`request_schema_url`: +: OPTIONAL. HTTPS URI. URL where the Access Request Service publishes a machine-readable description of the augmentations the PEP must add to the submission's `context` and `requested_access` objects. RECOMMENDED to be a JSON Schema {{I-D.bhutton-json-schema}} {{I-D.bhutton-json-schema-validation}} document. Suitable for autonomous PEPs and for PEPs that render forms natively against a schema. -An attacker could attempt to obtain approval for one resource and apply it to another. Implementations MUST bind Access Requests and approval results to the Subject, Resource, Action, Context, task, and requester. PDPs MUST validate this binding during re-evaluation. +A PDP implementing this profile MAY include either member in the requestable denial when the Access Request requires such fields. PEPs interacting with deployments that do not include either member MAY omit form-schema processing entirely. -### Approval Reference Substitution +When a deployment expects autonomous PEP submissions, the requestable denial SHOULD include `request_schema_url` referencing a JSON Schema {{I-D.bhutton-json-schema}} {{I-D.bhutton-json-schema-validation}} document that describes the augmentations the PEP MUST add to the submission's `context` and `requested_access` objects. An autonomous PEP MAY consume the schema directly to construct a valid submission; {{pep-processing-rules}} governs handling of inputs it cannot supply. -A hostile or compromised PEP could attempt to submit an `approval.id` or `approval.state` obtained from another Access Request during re-evaluation. An approval reference is not a bearer grant by itself. PDPs MUST resolve or verify the approval reference and confirm that it is bound to the authenticated caller or requester, current Subject, Resource, Action, relevant Context, approval scope, and approval expiry before using it as an input to an allow decision. Possession of a valid-looking approval identifier is insufficient to authorize access. +Implementations using proprietary form languages MAY publish a JSON Schema derived from their native form description. Translation can lose rendering details; the JSON Schema referenced by `request_schema_url` SHOULD provide enough information for an autonomous PEP to construct a conformant submission, while richer rendering, widget, and interaction details remain in `form_url`. -When approval state is carried by reference, the PDP or Access Request Service MUST protect the backing approval record against unauthorized lookup and mutation. When approval binding material is carried by value, for example in `approval.state`, the PDP MUST verify integrity, issuer, audience or intended recipient, expiry, and binding before accepting it. +The companion Catalog Profile {{CATALOG}} defines how a PEP resolves fields backed by application, entitlement, role, or cost-center catalogs. The denial references a Catalogs Document; the form schema defines the data shape. -### Binding Token Integrity {#binding-token-integrity} +This profile does not define a UI rendering vocabulary. Deployments that need richer rendering hints (such as widget selection, layout, or conditional display) MAY layer a UI vocabulary, identified out of band, typically keyed by `template`. -The `binding_token` member round-trips PDP-issued state through the PEP to the Access Request Service. Without integrity protection, a buggy or hostile PEP could drop, alter, or fabricate this value to influence approval routing or scope. PDPs MUST integrity-protect `binding_token` using a mechanism the Access Request Service can verify and SHOULD issue it as a JWS so the Access Request Service can prove the value was produced by the PDP and bound to the original denied evaluation. When the payload contains information that must not be visible to the PEP, the PDP MAY use JWE in addition to integrity protection, for example by encrypting a signed payload. This is a confused-deputy mitigation: it lets the Access Request Service confirm that the requestable-denial state was issued by the PDP and not fabricated or altered by the PEP. +This profile does not define an agent protocol surface. Deployments serving agentic PEPs MAY additionally expose Access Request submission through an agent protocol where the tool input schema corresponds to the JSON Schema referenced by `request_schema_url`. Discovery of such surfaces is out of scope for this specification. -This profile does not mandate a specific JWS payload; the contents are deployment-specific. Implementations that issue `binding_token` as a JWT SHOULD include the following claims to provide sound token hygiene and confused-deputy protection: +# Extensibility and Profiles {#extensibility} -* `iss`: PDP identifier. Lets the Access Request Service select the correct verification key from the PDP's JWK Set ({{discovery}}). -* `aud`: REQUIRED. Access Request Service identifier, or an array of identifiers including the Access Request Service. Array audiences support polyglot deployments that issue a single JWT consumed by multiple verifiers; the Access Request Service accepts the JWT when its identifier is among the listed audiences. Prevents replay of a token issued for one Access Request Service against another. The Access Request Service MUST reject a `binding_token` JWT that lacks `aud` or whose `aud` does not include the Access Request Service's identifier. -* `iat`, `exp`: issued-at and expiry. Expiry SHOULD be short (typically minutes, aligned with the requestable-denial hint lifetime). -* `jti`: unique token identifier. The Access Request Service SHOULD track recently-seen `jti` values until the token's `exp` to detect replay of an otherwise valid token; because `exp` is short (typically minutes), the replay-tracking window is correspondingly bounded. -* `denial_expires_at`: the `context.access_request.expires_at` value from the requestable denial, unless the token's `exp` is no later than that value. This lets the Access Request Service verify the PEP-echoed `denial.expires_at` value or enforce the token expiry as an equal-or-stricter freshness deadline. -* `binding_context_members`: the array of `context` member names that constitute the authorization-relevant Context for this evaluation (see the Terminology definition of Authorization-Relevant Context). Present (and MAY be an empty array) whenever any binding claim covers context; the Access Request Service uses exactly this integrity-protected set when comparing or hashing the authorization-relevant Context, and binds only Subject, Resource, and Action when it is absent. -* Binding claims that identify the original denied evaluation. For interoperability across independently implemented PDPs and Access Request Services, the inline form is RECOMMENDED, because it is compared structurally and requires no agreed byte canonicalization. Either: - * Inline (RECOMMENDED): the Subject, Resource, Action, and authorization-relevant Context of the denied evaluation, which the Access Request Service compares structurally, member by member, against the submission. Subject, Resource, and Action comparison includes the full AuthZEN Authorization API objects, including any `properties` members present in the bound values, except that `subject.properties.act` is excluded because the PEP MAY normalize the actor to `client.actor` ({{delegation}}). Context comparison includes each member of the authorization-relevant Context and excludes profile machinery members. - * Hashed: a `binding_hash` whose value is the base64url-encoded (without padding) SHA-256 digest of the {{RFC8785}} JSON Canonicalization Scheme (JCS) serialization of the JSON object `{"subject": , "resource": , "action": , "context": }`, where `` is the bound Subject with `subject.properties.act` removed (matching the inline form's exclusion) and `` is the enumerated set, which the Access Request Service recomputes from the submission. Implementations that use the hashed form MUST use exactly this construction so that a PDP and an independently implemented Access Request Service compute identical digests. -* `evaluation_id`: the PDP's identifier for the evaluation, when present in `context.evaluation_id` ({{evaluation-identifier}}). +Extension points allow profiles and deployments to adapt the wire format to upstream protocols, governance platforms, and request interfaces. + +This document defines both a reusable AuthZEN extension and a profile of its use. The extension is the AuthZEN surface: `context.access_request`, `context.evaluation_id`, `context.evaluated_at`, and `context.reason` on a denied Decision; `context.approval` on an Access Evaluation request; and `next_action`, `retry_after`, `reason`, and the approval members on a re-evaluation Decision. The profile is the Access Request Endpoint, the submission, the Task Handle, and the completion mode. Unrecognized members of the extension do not change the underlying AuthZEN Decision: a PEP that does not implement this profile sees a plain denial or a plain permit. Recognized members have the semantics this profile defines, including `context.approval` as authorization input to a PDP and `approved_until` as an enforcement bound on a PEP. The handling of each member when unrecognized is stated with the member and summarized in {{decision-context-members}}. + +## Companion-Defined Members {#companion-profiles} + +The following protocol members and status value are defined by normative reference to companion profiles. Their presence, processing, and validation rules are specified in those profiles. + +| Location | Member or value | Specification | +|---|---|---| +| Access Request submission | `items` | Bulk Access Requests {{BULK}} | +| Task Handle | `items`; `partial` value of `status` | Bulk Access Requests {{BULK}} | +| Access Request submission | `callback` | Callback Notifications {{CALLBACK}} | + +A normative reference to a companion profile makes its definitions authoritative when the feature is used; conformance to this profile does not require implementing a companion unless a rule here says so. These are defined protocol names, not unrecognized extension names under the forward-compatibility rule. The companion definitions do not open the submission or Task Handle to arbitrary additional members. + +## Extension Points -Throughout this profile, structural comparison of two JSON values treats them as equal when they have the same JSON type and: numbers are equal under their {{RFC8785}} canonical form; strings are equal codepoint-for-codepoint; arrays are equal element-by-element in order; objects are equal when they have the same set of member names with recursively equal member values; and an absent member is distinct from a member whose value is `null`. This is the comparison used wherever this profile compares Subject, Resource, Action, or authorization-relevant Context, including inline denial binding and approval-scope matching ({{completion-semantics}}). +Additional members beyond those defined in this document or by {{companion-profiles}} MAY appear only at the following locations, and those members MUST follow the naming rules in {{extension-naming}}. No other object members may be extended without a revision of this specification or a profile that explicitly redefines them. The AuthZEN extensibility model proposes a recursive rule under which any named object carries registered, namespaced members; aligning this list with that rule is a decision for a revision of this specification. -When `items` is present in the submission (bulk), the binding claims cover the entire `items` array and authorization-relevant Context. Inline bulk binding claims list each submitted item, including the full Resource and Action objects for that item, in the same order as the bound Access Request. A bulk `binding_hash` is the base64url-encoded (without padding) SHA-256 digest of the JCS serialization of the JSON object `{"subject": , "items": [{"resource": , "action": }, ...], "context": }`, where `` is the bound Subject with `subject.properties.act` removed and the `items` array order is the order bound by the denial. Implementations that use a bulk hashed form MUST use exactly this construction. When every item carries its own per-item `denial`, each per-item binding is verified using the single-item rules instead of this bundle construction. +* `context.access_request`: additional members of the requestable denial, such as URLs of profile-defined companion documents the PEP consults when constructing a submission. +* `context.access_request.display`: user-interface hints in a requestable denial. +* AuthZEN Decision Context members defined by this profile. +* `context` in an Access Request submission: augments the AuthZEN Context. +* `requested_access` in an Access Request submission. +* `client` in an Access Request submission. +* `task.display`: user-interface hints attached to a Task Handle. +* `task.links`: link relations to related URLs. +* `result` and the additions defined under each `result.mode`. +* `approval.state` in a result under the `reevaluate` completion mode: opaque profile-specific or deployment-specific verifier state carried through the PEP to the PDP at re-evaluation time. -PDPs MAY add deployment-specific claims (policy version, factors, risk score, tenant identifier) when the Access Request Service needs them for routing or audit. When such claims must remain opaque to the PEP, the PDP wraps the signed payload in JWE encrypted to the Access Request Service. +This specification also defines extensibility for enumerated values: -When `binding_token` is a JWS-signed JWT using these claims, the Access Request Service, on receipt: +* New values for `task.status` ({{task-status}}). +* New values for `result.mode` ({{completion-semantics}}). +* New problem types for {{RFC9457}}-style error responses ({{error-responses}}). -1. parses the JWS header and resolves the verification key from the JWK Set at the PDP's `jwks_uri`; -2. verifies the signature, the `aud` claim, and the expiry; -3. checks `jti` against recently-seen tokens to detect replay; -4. compares the binding claims (inline or hashed) against the submission's Subject, Resource, Action, and authorization-relevant Context (or per-item for bulk submissions), rejecting a mismatch with `urn:openid:authzen:access-request:error:invalid_denial_binding`; -5. enforces freshness (the earlier of the token `exp` and `denial.expires_at`), rejecting a submission past that deadline with `urn:openid:authzen:access-request:error:expired_denial`. +This specification does not create registries for these enumerated values. Specifications that define new values for `task.status`, `result.mode`, or problem types SHOULD define stable names or URIs and processing rules for those values. Short, unqualified names for `result.mode` are reserved for values defined by this base specification or by a future registry; profile-defined `result.mode` values SHOULD use absolute URIs unless such a registry exists. -When the `binding_token` carries its own expiry (`exp`) and the submitted denial also carries `denial.expires_at`, the Access Request Service MUST enforce the earlier of the two as the freshness deadline for the submission. When `denial_expires_at` or equivalent protected binding material is present, the Access Request Service MUST verify that `denial.expires_at` matches the protected value before relying on it. When no protected denial-expiry value is present, the Access Request Service MUST rely on `exp` only if it is no later than the echoed `denial.expires_at`; otherwise the binding material is insufficient to prove the freshness window and the submission MUST be rejected with `urn:openid:authzen:access-request:error:invalid_denial_binding`. A submission whose freshness deadline has passed MUST be rejected with `urn:openid:authzen:access-request:error:expired_denial`. +## Decision Context Members {#decision-context-members} -When `binding_token` uses another integrity-protected format, the Access Request Service MUST perform equivalent verification for issuer authenticity, audience or intended recipient, expiry when present, replay resistance when provided by the format, and binding to the submitted Subject, Resource, Action, and relevant Context. +The AuthZEN Decision Context and request Context members this document defines, with their processing strength and the handling of an unrecognized member or value, each stated normatively in the section cited: -A single signed JWT MAY simultaneously satisfy this profile's claim recommendations and the requirements of another profile or specification that uses the same JWT, provided the union of required claims is present and consistent. This enables polyglot deployments that issue one artifact and surface it on multiple wire formats (for example, as `context.access_request.binding_token` in an AuthZEN Authorization API response and as a profile-defined token elsewhere). Verifiers process only the claims they understand and tolerate additional profile-specific claims without rejecting the JWT. +| Member | Position | Effect when recognized | Unrecognized member or value | +|---|---|---|---| +| `context.access_request` | denied Decision | the denial is requestable | absent or unrecognized: the denial is not requestable ({{pep-recognize}}) | +| `context.evaluation_id`, `context.evaluated_at`, `context.reason` | denied Decision | echoed in the submission | echoed when present, otherwise absent from the submission ({{pep-construct}}) | +| `context.approval` | Access Evaluation request | authorization input the PDP verifies | a PDP that cannot resolve or verify it evaluates as not approved by that reference ({{approval-verification}}) | +| `next_action`, `retry_after`, `reason` | re-evaluation Decision; `reason` also on a requestable denial | directs the PEP's next step | unrecognized `next_action` or `reason` falls back as defined in {{pep-reevaluation-handling}}; the Decision itself is unchanged | +| response-side `approval` members | re-evaluation Decision | `approved_until` bounds reuse and enforcement | unrecognized: the permit stands as an ordinary AuthZEN permit ({{approval-lifetime}}) | -For cross-vendor interoperability, an Access Request Service MUST support verifying a `binding_token` presented as a JWS in compact serialization, and a PDP MUST support verifying an `approval.state` presented as a JWS in compact serialization ({{completion-semantics}}). Other integrity-protected formats MAY be used when both the issuer and the verifier support them. +No member in this table widens a denial or narrows a permit for a PEP that does not implement this profile; in particular, `approved_until` is an enforcement and reuse bound for an implementer of this profile and does not narrow an ordinary AuthZEN permit for any other PEP. Composition with other extensions that add Decision Context members, such as obligations, is not defined by this document. -### Approval Replay +## Naming Extensions {#extension-naming} -Approval references can be replayed if not time-bounded. Approval results MUST expire. Re-evaluation Mode SHOULD bind approval references to the original request tuple. Profiles of this specification that define token-based completion modes are responsible for defining the token's audience restriction, lifetime, and binding to the approved request. +A member name or value added at an extension point MUST be one of the following: -## Policy and Approver Hygiene +1. A name registered in the AuthZEN Access Request Member Names registry ({{iana-member-names}}). Registry-eligible names are short, lowercase, snake_case identifiers carrying semantics that are useful across multiple implementations. +2. An absolute URI (HTTPS or URN) when the member is profile-specific and not appropriate for the registry. Profiles SHOULD use a stable URI under the profile's change controller. +3. A reverse-DNS-prefixed identifier (for example, `vendor.example.com/foo`) when the member is private to a single deployment and not intended for cross-implementation use. -### Overbroad Approval {#overbroad-approval} +The contents of `approval.state` are opaque to this specification and are not subject to the member naming requirements above unless a profile or deployment explicitly defines structure within `approval.state`. -This profile does not define an approval policy language. Implementations MUST NOT treat the `template`, `requested_access`, or `display` fields as sufficient authorization policy. Actual approval scope and enforcement semantics are determined by the PDP and Access Request Service. +## Forward Compatibility -### Approver Eligibility and Separation of Duties {#approver-eligibility} +An implementation receiving a member or value it does not recognize at an extension point MUST ignore it and MUST NOT fail processing on the basis of the unrecognized name. This default does not override fail-safe rules defined elsewhere in this profile, such as the PEP rule in {{pep-processing-rules}} that treats unknown `result.mode` values as not approved rather than as ignorable. An implementation MAY surface unrecognized members in audit records or pass them through unchanged when echoing wire content (for example, in callbacks). -Approval workflows can violate enterprise access policy if an approver is not eligible to approve the requested access. Access Request Services MUST evaluate approver eligibility before returning `approved`, including self-approval restrictions, delegated approver authority, separation-of-duties constraints, ownership rules, and conflict-of-interest policy. A workflow step completed by an ineligible approver MUST NOT be treated as successful approval unless local policy explicitly allows that exception and records it for audit. +## Profiles -### Emergency Access +A profile is a separate specification defining extensions for a use case, such as OAuth 2.0 token requests, Rich Authorization Requests {{?RFC9396}}, or integration with a governance platform. -The `requested_access.emergency` member is a request signal, not an authorization override. Implementations that support emergency or break-glass access SHOULD require a business justification, apply the shortest practical approval or access lifetime, notify appropriate owners or security personnel, and require post-use review. Emergency requests and approvals SHOULD be retained and auditable according to the deployment's security and compliance policy. +A profile SHOULD: -## Information Disclosure +* Identify itself with a stable URI. +* Specify the extension points it populates and the member names or enumerated values it introduces. +* Register registry-eligible member names in the AuthZEN Access Request Member Names registry ({{iana-member-names}}). +* Define semantics, validation rules, and any normative requirements for its members. +* Enumerate any constraints it places on members or behaviors defined by this base specification. -### Trusting URLs from the Requestable Denial +This specification neither enumerates profiles nor requires declarative profile negotiation. Conformance to a profile depends on the presence and processing of its registered or namespaced members. -The `endpoint`, `form_url`, `request_schema_url`, `request_catalogs_url`, and the catalog `endpoint` values inside a Catalogs Document are all delivered to the PEP inside a denial response or document fetched on the basis of that response. A compromised or misconfigured PDP, or an Access Request Service compelled by one, could direct the PEP at attacker-controlled hosts to harvest justifications, render hostile UI, substitute schemas and catalogs, or perform credential phishing against the requester. +# Security Considerations -An autonomous PEP MUST verify that these URLs resolve to hosts trusted under the deployment before fetching or acting on them, by requiring the same origin as the Access Request Endpoint advertised in PDP metadata or by maintaining an explicit allowlist of trusted Access Request Service hosts; a PEP that renders them for a human user SHOULD apply the same check. PEPs MUST NOT submit credentials to a host that is not trusted to receive them. +This section describes threats and cites their mitigations. It introduces no requirements. -### Catalog Disclosure +## Denial and Approval Integrity -Catalog Endpoints ({{catalog-references}}) can leak sensitive information about applications, entitlements, organizational structure, or finance master data if not properly authorized. An attacker who can call a Catalog Endpoint without scoping or authorization can enumerate sensitive identifiers, infer access policy, or harvest catalog metadata. +**Denial remains denial.** Treating `context.access_request` as permission would grant access without an allow decision. The PEP rules in {{pep-processing-rules}} preserve denial and treat unknown task statuses and completion modes as not approved. -Mitigations: +**Confused deputy and request substitution.** An attacker could substitute a Subject or Resource. Submission checks compare signed denial claims ({{verifying-denial-binding}}) or, when no token is present, recorded state ({{shared-state-deployments}}). Task binding covers the denial, requester, and client ({{ars-processing-rules}}), and re-evaluation checks approval applicability and scope ({{approval-verification}} and {{approval-scope}}). -* Catalog Endpoints MUST authorize callers and MUST return only items the caller is permitted to see for the original Subject, Resource, and Action. -* Catalog Endpoints SHOULD apply rate limits and abuse detection commensurate with the sensitivity of the catalog they expose. -* PEPs SHOULD prefer searching with `search_param` over bulk enumeration. +**Binding-token integrity.** A buggy or hostile PEP could alter or fabricate PDP-issued state to influence approval routing or scope. {{binding-token-integrity}} and {{verifying-denial-binding}} define integrity, binding, and freshness checks; {{denial-binding-alternatives}} covers other formats. -### Task Handle Leakage {#task-handle-leakage} +**Approval reference substitution and replay.** A compromised PEP could present another request's approval or replay an expired or inapplicable reference. {{approval-verification}} defines applicability checks; {{decision-and-binding-integrity}} defines verification of by-value material and protection of backing records. An approval reference is not a bearer grant. -Task handles can reveal workflow state or be used to poll for sensitive information. Task handles MUST be opaque, unguessable, and protected by authentication and authorization checks. A leaked task handle MUST NOT be sufficient to retrieve task status without caller authorization. +## Policy and Approver Hygiene -### PEP-Facing vs End-Client-Facing Surfaces +**Overbroad approval.** Treating `template`, `requested_access`, or `display` as sufficient policy would let a requester set their own approval scope. These members are request signals, not authorization policy ({{overbroad-approval}}). -Several members of the task response and approval result are intended for PEP-to-Access-Request-Service or PEP-to-PDP machine interactions, not for direct use by end clients (browsers, mobile applications, agent runtime UIs, or other non-PEP callers acting on behalf of the Subject). The following are PEP-facing: +**Approver eligibility and separation of duties.** Self-approval, missing delegated authority, and conflicts of interest or duties can make a completed workflow invalid. {{approver-eligibility}} requires eligibility checks and permits exceptions only when local policy explicitly allows and audits them. -* `task.status_endpoint`: the polling URL for the Access Request Service. -* `task.links.cancel`: the cancellation endpoint. -* `approval.id` and `approval.state`: round-trip material the PEP places at `context.approval` during re-evaluation. +**Emergency access.** Treating `requested_access.emergency` as an override would bypass policy. {{overbroad-approval}} describes justification, limited lifetime, notification, review, and audit for emergency access. -PEPs SHOULD NOT forward these members to end clients or other non-PEP callers. Forwarding `status_endpoint` or `links.cancel` creates a direct end-client-to-Access-Request-Service channel that bypasses the PEP's enforcement and authorization context; forwarding `approval.id` or `approval.state` allows an end client to attempt to inject the approval reference into other PEPs or other evaluations. Possession of these values is not itself authorization (see {{task-handle-leakage}} and the PDP applicability rules in {{completion-semantics}}), but exposing them broadens the attack surface unnecessarily. +## Information Disclosure -Human-facing surfaces are conveyed separately and are intended to be rendered only to callers authorized for the corresponding human workflow: +**Trusting URLs from the requestable denial.** A compromised or misconfigured PDP or Access Request Service could direct the PEP to hostile endpoints, forms, or schemas to harvest information or credentials. {{trusting-urls}} defines host-trust checks for denial-supplied URLs and URLs in documents fetched from them. -* `task.links.ticket`: URL where the requester (Subject) can view the request and its status. -* `task.links.review`: URL where an approver or administrator can review or act on the request. -* `task.display`: localizable user-interface hints. +**Task handle leakage.** A leaked handle could expose workflow state or sensitive information. Opacity and unguessability do not replace authorization for each operation ({{task-handle-leakage}} and {{authorization-and-authentication}}). Cancellation requires separate authorization ({{cancellation}}). -When a PEP renders requester-facing status to an end client, it SHOULD do so by rendering `task.display` and `task.links.ticket` rather than by exposing the machine surfaces. A PEP MUST NOT expose `task.links.review` to a requester or other end client unless that caller has been authenticated and authorized as an approver or administrator for the task. +**PEP-facing and end-client-facing surfaces.** Exposed machine endpoints let end clients bypass the PEP; exposed approval references enable injection attempts in other evaluations. {{pep-facing-surfaces}} separates machine and human-facing members and restricts disclosure of approver links. ## Operational and Integration -### Callback Security +**PEP acting on behalf of the Subject.** Accepting unverified actor claims would let a PEP assert authority it cannot demonstrate. {{endpoint-protection}} requires caller authorization to submit or view the request for the supplied Subject, Resource, and Action; actor-chain verification is defined by {{ACTOR}}. -Callback endpoints can be abused for spoofing, replay, request forgery, and server-side request forgery. Access Request Services MUST validate callback destinations as described in {{callback-completion}}. Callback notifications MUST be authenticated. PEPs SHOULD verify callback origin, bind callbacks to expected task identifiers and state values, and treat callbacks as notifications unless they contain an enforceable result under this profile. +**Idempotency-key abuse.** Caller-supplied keys consume server-side state and are matched against retries. {{idempotency-key-abuse}} gives scoping and retention guidance. -### PEP Acting on Behalf of the Subject +**Availability.** Approval workflows introduce latency and dependencies on external systems. {{pep-poll}} and {{availability}} cover fail-closed behavior and rate limiting; {{endpoint-protection}} covers endpoint authentication and authorization. -A PEP submitting an Access Request typically acts on behalf of the Subject identified in the original AuthZEN Authorization API evaluation, and may act on behalf of a longer delegation chain. The verification model the Access Request Service applies, the credential the PEP presents, and the wire representation of the delegation chain are defined in {{delegation}}. An Access Request Service that accepts unverified actor claims weakens the trust model of the entire flow; submissions whose claimed chain cannot be verified MUST be rejected. - -### Idempotency Key Abuse +# Privacy Considerations {#privacy-considerations} -Idempotency keys can be used to correlate requests. Implementations SHOULD scope idempotency keys to the authenticated caller and avoid storing them longer than necessary. +Access Requests may contain sensitive information, including user identifiers, resource identifiers, business justifications, approval chains, and policy reasons. Implementations SHOULD minimize the amount of information returned to the PEP and displayed to the end user. -### Availability +The Access Request Service SHOULD separate end-user display reasons from administrator diagnostic reasons. A requestable denial response SHOULD avoid exposing internal policy identifiers unless the PEP is authorized for administrative diagnostics. -Approval workflows can introduce latency and dependency on external systems. PEPs SHOULD fail closed when task status cannot be determined. Access Request Services SHOULD apply rate limits and abuse detection to request submission and polling endpoints. +Approval records SHOULD be retained only as long as required by business, security, and compliance policy. # IANA Considerations @@ -1453,6 +1452,7 @@ Change Controller: Specification Document: : This document. + ## AuthZEN Policy Decision Point Capabilities Registry This specification requests registration of the following PDP capabilities in the AuthZEN Policy Decision Point Capabilities Registry. @@ -1472,19 +1472,19 @@ Change Controller: Specification Document: : This document. -The capability URN registered above and the problem-type URNs used for error responses ({{error-responses}}) both use the `urn:openid:authzen:` namespace administered by the OpenID Foundation, rather than the `urn:ietf:params:authzen:` sub-namespace that the AuthZEN Authorization API registers for capabilities ({{AuthZEN}}). This is an intentional OpenID Foundation profile convention; the `capabilities` array remains a list of URNs as defined by {{AuthZEN}}. +The capability and problem-type URNs ({{error-responses}}) use the OpenID Foundation's `urn:openid:authzen:` namespace, not AuthZEN's `urn:ietf:params:authzen:` capability sub-namespace. This profile convention leaves the AuthZEN `capabilities` array as a list of URNs ({{AuthZEN}}). ## AuthZEN Access Request Member Names Registry {#iana-member-names} This specification requests creation of a new registry: the AuthZEN Access Request Member Names registry. -The registry tracks well-known member names that may appear at the extension points defined in {{extensibility}}. Registration policy is Specification Required. Each entry has the following fields: +The registry tracks well-known member names that may appear at the extension points defined in {{extensibility}}. In the terms of the AuthZEN extensibility model this is a member-name registry; each entry's processing strength is that of the object it extends, as stated by its specification document. Registration policy is Specification Required. Each entry has the following fields: Name: : The member name as it appears on the wire. Extension Point: -: One of the extension points listed in {{extensibility}}. +: One of the extension points listed in {{extensibility}}, or an extension point defined by a profile of this specification. Description: : A short description of the member's semantics. @@ -1501,13 +1501,6 @@ Initial entries registered by this specification: |---|---|---| | `requested_until` | `requested_access` | RFC 3339 timestamp requesting access through a specific absolute time. | | `emergency` | `requested_access` | Boolean requesting an expedited or emergency-access path. | -| `session_id` | `client.source` | Identifier of a bounded interaction context that produced the request (chat or agent conversation, application session, CLI invocation, workflow thread). | -| `external_url` | `client.source` | URL of an external system that motivated the request. | -| `integration_id` | `client.source` | Identifier of an upstream integration or workflow that produced the request. | -| `description` | Catalog Item | Human-readable description of the catalog item. | -| `risk_level` | Catalog Item | Risk classification used by the deployment. | -| `granted` | Catalog Item | Boolean indicating the requester already has access to the item. | -| `owner` | Catalog Item | Identifier or reference for the item's owner. | | `ticket` | `task.links` | URL where the requester can view the request and its status. | | `review` | `task.links` | URL where an approver or administrator can review or act on the request. | | `cancel` | `task.links` | URL where the PEP can cancel the request. | @@ -1525,13 +1518,13 @@ Change Controller for all initial entries: OpenID Foundation AuthZEN Working Gro This specification requests creation of a new registry: the AuthZEN Access Request Re-evaluation Denial Reason registry. -The registry tracks well-known `context.reason` values a PDP returns when it denies a re-evaluation that presented an `approval` reference ({{completion-semantics}}). Registration policy is Specification Required. Each entry has the following fields: +The registry tracks well-known `context.reason` values a PDP returns when it denies a re-evaluation that presented an `approval` reference ({{completion-semantics}}). In the terms of the AuthZEN extensibility model this is an enumerated-type registry whose values are advisory. Registration policy is Specification Required. Each entry has the following fields: Reason: : The `context.reason` value as it appears on the wire. Default Next Action: -: The RECOMMENDED `next_action` for the value: `request`, `retry`, or `none`. +: The RECOMMENDED `next_action` for the value, which a PEP applies when the PDP's response carries no recognized `next_action`: `request`, `retry`, or `none`. Description: : A short description of the denial condition. @@ -1542,7 +1535,7 @@ Change Controller: Specification Document: : The document defining the value. -Initial entries registered by this specification: +Initial entries, summarizing the semantics and default next actions defined in {{reevaluation-denials}}: | Reason | Default Next Action | Description | |---|---|---| @@ -1558,9 +1551,11 @@ Change Controller for all initial entries: OpenID Foundation AuthZEN Working Gro # Examples -## End-to-End Manager Approval +## End-to-End Trusted-State Approval {#trusted-state-walkthrough} + +This non-normative walkthrough uses trusted state at both backend boundaries: the service resolves the denied evaluation by `evaluation_id`, and the PDP resolves the approval by `approval.id`. The authorization-relevant Context for this example is empty; submission-time business justification is workflow input. -### Initial Evaluation Request +### Initial Evaluation Request {#trusted-initial-evaluation-request} ~~~ http POST /access/v1/evaluation HTTP/1.1 @@ -1586,7 +1581,7 @@ Content-Type: application/json } ~~~ -### Requestable Denial +### Requestable Denial {#trusted-requestable-denial} ~~~ http HTTP/1.1 200 OK @@ -1600,17 +1595,13 @@ Content-Type: application/json "reason": "approval_required", "access_request": { "template": "manager_approval", - "expires_at": "2026-04-30T20:25:00Z", - "binding_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBkcC0xIn0.eyJldmFsdWF0aW9uX2lkIjoiZXZhbF8wMUhYNFkyUDhCUTRZM0YwVjBLOUQ2WjdNMSJ9.bXBfc2lnbmF0dXJl", - "form_url": "https://requests.example.com/forms/manager_approval", - "request_schema_url": "https://requests.example.com/schemas/manager_approval.json", - "request_catalogs_url": "https://requests.example.com/catalogs/manager_approval.json" + "expires_at": "2026-04-30T20:25:00Z" } } } ~~~ -### Submitting the Access Request +### Submitting the Access Request {#trusted-submitting-the-access-request} ~~~ http POST /access/v1/requests HTTP/1.1 @@ -1642,13 +1633,12 @@ Idempotency-Key: 7b8d0f0d-65a1-4af1-9fd3-a684f08a5d13 "evaluated_at": "2026-04-30T20:15:00Z", "expires_at": "2026-04-30T20:25:00Z", "reason": "approval_required", - "binding_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBkcC0xIn0.eyJldmFsdWF0aW9uX2lkIjoiZXZhbF8wMUhYNFkyUDhCUTRZM0YwVjBLOUQ2WjdNMSJ9.bXBfc2lnbmF0dXJl", "template": "manager_approval" } } ~~~ -### Task Handle +### Task Handle {#trusted-task-handle} ~~~ http HTTP/1.1 202 Accepted @@ -1672,7 +1662,7 @@ Location: https://pdp.example.com/access/v1/requests/arq_01HX4Y3AJZ7Y56W2F9H8Q8C } ~~~ -### Completed Task +### Completed Task {#trusted-completed-task} ~~~ http HTTP/1.1 200 OK @@ -1681,21 +1671,21 @@ Content-Type: application/json { "task": { "id": "arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4", - "status": "approved" + "status": "approved", + "status_endpoint": "https://pdp.example.com/access/v1/requests/arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4" }, "result": { "mode": "reevaluate", "approval": { "id": "apr_01HX4Y8E2NE3Y2X7P0K4JE6WVH", "approved_at": "2026-04-30T20:42:00Z", - "approved_until": "2026-05-01T00:42:00Z", - "state": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImFycy0xIn0.eyJhcHByb3ZhbF9pZCI6ImFwcl8wMUhYNFk4RTJORTNZMlg3UDBLNEpFNldWSCJ9.c2lnbmF0dXJl" + "approved_until": "2026-05-01T00:42:00Z" } } } ~~~ -### Re-evaluation After Approval +### Re-evaluation After Approval {#trusted-re-evaluation-after-approval} The re-evaluation request does not repeat the original `evaluation_id`. The PDP resolves the `approval.id` (and `approval.state`, when present) to the approved Access Request task and original denied evaluation. @@ -1728,7 +1718,7 @@ Content-Type: application/json } ~~~ -### Final Decision +### Final Decision {#trusted-final-decision} ~~~ http HTTP/1.1 200 OK @@ -1745,49 +1735,12 @@ Content-Type: application/json } ~~~ -## End-to-End Agent Tool Discovery - -This non-normative example shows an AI agent acting on behalf of a user that, mid-task, requires authority to invoke a previously undeclared downstream tool. A broad-scope approval grants authority for a class of subsequent same-class invocations, so the agent does not produce a new Access Request for each related call. - -### Initial Evaluation Request - -The agent attempts to invoke a CRM search tool while assembling a renewal report. +## End-to-End Manager Approval with Binding Artifacts -~~~ http -POST /access/v1/evaluation HTTP/1.1 -Host: pdp.example.com -Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA -Content-Type: application/json - -{ - "subject": { - "type": "user", - "id": "alice@example.com", - "properties": { - "act": { - "iss": "https://agents.example.com", - "sub": "agent_renewal_assistant_v3", - "sub_profile": "ai_agent" - } - } - }, - "resource": { - "type": "tool", - "id": "crm.search_accounts" - }, - "action": { - "name": "invoke" - }, - "context": { - "time": "2026-05-12T15:00:00Z" - } -} -~~~ +This walkthrough differs from {{trusted-state-walkthrough}} only where the deployment uses binding artifacts: the denial carries a `binding_token` (and, in this example, `form_url` and `request_schema_url`), the submission echoes the token, and the approval carries `state`. The other exchanges are identical to the trusted-state walkthrough. ### Requestable Denial -The PDP returns a denial requesting broad-scope approval for the agent to call CRM tools. - ~~~ http HTTP/1.1 200 OK Content-Type: application/json @@ -1795,14 +1748,15 @@ Content-Type: application/json { "decision": false, "context": { - "evaluation_id": "eval_01HX6A9D2M7N0F4G3K2T9P1B8X", - "evaluated_at": "2026-05-12T15:00:00Z", - "reason": "agent_authority_missing", + "evaluation_id": "eval_01HX4Y2P8BQ4Y3F0V0K9D6Z7M1", + "evaluated_at": "2026-04-30T20:15:00Z", + "reason": "approval_required", "access_request": { - "template": "agent_tool_class_approval", - "expires_at": "2026-05-12T15:10:00Z", - "binding_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBkcC0xIn0.eyJldmFsdWF0aW9uX2lkIjoiZXZhbF8wMUhYNkE5RDJNN04wRjRHM0syVDlQMUI4WCIsImNsYXNzIjoiY3JtX3Rvb2xzIn0.aGFzaA", - "request_schema_url": "https://requests.example.com/schemas/agent_tool_class_approval.json" + "template": "manager_approval", + "expires_at": "2026-04-30T20:25:00Z", + "binding_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBkcC0xIn0.eyJldmFsdWF0aW9uX2lkIjoiZXZhbF8wMUhYNFkyUDhCUTRZM0YwVjBLOUQ2WjdNMSJ9.bXBfc2lnbmF0dXJl", + "form_url": "https://requests.example.com/forms/manager_approval", + "request_schema_url": "https://requests.example.com/schemas/manager_approval.json" } } } @@ -1810,14 +1764,12 @@ Content-Type: application/json ### Submitting the Access Request -The agent's runtime submits a request and supplies actor and source members so the Access Request Service can route approval to the agent's owner and record the session that triggered the request. The agent persists the Task Handle, releases the calling thread, and continues other in-flight work; approval may take minutes to days, and execution resumes when the callback fires. - ~~~ http POST /access/v1/requests HTTP/1.1 Host: pdp.example.com Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA Content-Type: application/json -Idempotency-Key: 9c1f5d12-2a18-4cba-8a5e-e0e8e2b6b5c7 +Idempotency-Key: 7b8d0f0d-65a1-4af1-9fd3-a684f08a5d13 { "subject": { @@ -1825,280 +1777,317 @@ Idempotency-Key: 9c1f5d12-2a18-4cba-8a5e-e0e8e2b6b5c7 "id": "alice@example.com" }, "resource": { - "type": "tool", - "id": "crm.search_accounts" + "type": "document", + "id": "q4-plan" }, "action": { - "name": "invoke" + "name": "can_read" }, "context": { - "business_justification": "Assembling Q2 renewal report for customer ACME-1042" + "business_justification": "Needed for customer renewal review" }, "requested_access": { - "requested_until": "2026-05-19T15:00:00Z" - }, - "client": { - "id": "renewal_assistant", - "actor": { - "id": "agent_renewal_assistant_v3", - "issuer": "https://agents.example.com", - "type": "ai_agent" - }, - "source": { - "session_id": "session_01HX69WJ8Q0K7P4F0V0K9D6Z7N" - } - }, - "callback": { - "endpoint": "https://agents.example.com/callbacks/access-requests", - "state": "session_01HX69WJ8Q0K7P4F0V0K9D6Z7N", - "events": ["approved", "denied", "expired"] + "requested_until": "2026-05-01T00:15:00Z" }, "denial": { - "evaluation_id": "eval_01HX6A9D2M7N0F4G3K2T9P1B8X", - "evaluated_at": "2026-05-12T15:00:00Z", - "expires_at": "2026-05-12T15:10:00Z", - "reason": "agent_authority_missing", - "binding_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBkcC0xIn0.eyJldmFsdWF0aW9uX2lkIjoiZXZhbF8wMUhYNkE5RDJNN04wRjRHM0syVDlQMUI4WCIsImNsYXNzIjoiY3JtX3Rvb2xzIn0.aGFzaA", - "template": "agent_tool_class_approval" - } -} -~~~ - -### Task Handle - -~~~ http -HTTP/1.1 202 Accepted -Content-Type: application/json -Location: https://pdp.example.com/access/v1/requests/arq_01HX6AAB3J7Y56W2F9H8Q8C1V7 - -{ - "task": { - "id": "arq_01HX6AAB3J7Y56W2F9H8Q8C1V7", - "status": "pending", - "status_endpoint": "https://pdp.example.com/access/v1/requests/arq_01HX6AAB3J7Y56W2F9H8Q8C1V7", - "expires_at": "2026-05-20T00:00:00Z" + "evaluation_id": "eval_01HX4Y2P8BQ4Y3F0V0K9D6Z7M1", + "evaluated_at": "2026-04-30T20:15:00Z", + "expires_at": "2026-04-30T20:25:00Z", + "reason": "approval_required", + "binding_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBkcC0xIn0.eyJldmFsdWF0aW9uX2lkIjoiZXZhbF8wMUhYNFkyUDhCUTRZM0YwVjBLOUQ2WjdNMSJ9.bXBfc2lnbmF0dXJl", + "template": "manager_approval" } } ~~~ -### Approval Callback - -Hours later, after the agent's owner approves the request, the Access Request Service notifies the agent's callback endpoint. The callback is notification-only; the agent retrieves the Task Status Endpoint before enforcing access. +### Completed Task ~~~ http -POST /callbacks/access-requests HTTP/1.1 -Host: agents.example.com -Authorization: Bearer mF_9.B5f-4.1JqM +HTTP/1.1 200 OK Content-Type: application/json { - "state": "session_01HX69WJ8Q0K7P4F0V0K9D6Z7N", "task": { - "id": "arq_01HX6AAB3J7Y56W2F9H8Q8C1V7", + "id": "arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4", "status": "approved", - "status_endpoint": "https://pdp.example.com/access/v1/requests/arq_01HX6AAB3J7Y56W2F9H8Q8C1V7" + "status_endpoint": "https://pdp.example.com/access/v1/requests/arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4" + }, + "result": { + "mode": "reevaluate", + "approval": { + "id": "apr_01HX4Y8E2NE3Y2X7P0K4JE6WVH", + "approved_at": "2026-04-30T20:42:00Z", + "approved_until": "2026-05-01T00:42:00Z", + "state": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImFycy0xIn0.eyJhcHByb3ZhbF9pZCI6ImFwcl8wMUhYNFk4RTJORTNZMlg3UDBLNEpFNldWSCJ9.c2lnbmF0dXJl" + } } } ~~~ -### Completed Task +### Re-evaluation After Approval -The agent retrieves the completed task and obtains an approval reference scoped to the CRM tool class for seven days. This example includes `approval.state` to show a deployment where the PDP verifies integrity-protected binding material during re-evaluation rather than relying only on a server-side lookup by `approval.id`. +The re-evaluation request does not repeat the original `evaluation_id`. The PDP resolves the `approval.id` (and `approval.state`, when present) to the approved Access Request task and original denied evaluation. ~~~ http -HTTP/1.1 200 OK +POST /access/v1/evaluation HTTP/1.1 +Host: pdp.example.com +Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA Content-Type: application/json { - "task": { - "id": "arq_01HX6AAB3J7Y56W2F9H8Q8C1V7", - "status": "approved" + "subject": { + "type": "user", + "id": "alice@example.com" }, - "result": { - "mode": "reevaluate", + "resource": { + "type": "document", + "id": "q4-plan" + }, + "action": { + "name": "can_read" + }, + "context": { + "time": "2026-04-30T20:43:00Z", "approval": { - "id": "apr_01HX6BCEF8K3Z2X7P0K4JE6WVK", - "approved_at": "2026-05-12T17:30:00Z", - "approved_until": "2026-05-19T17:30:00Z", - "state": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBkcC0xIn0.eyJhcHByb3ZhbF9pZCI6ImFwcl8wMUhYNkJDRUY4SzNaMlg3UDBLNEpFNldWSyIsInNjb3BlIjoiY3JtX3Rvb2xzIiwiZXhwIjoxNzc5MjEwMDAwfQ.c2lnbmF0dXJl" + "id": "apr_01HX4Y8E2NE3Y2X7P0K4JE6WVH", + "approved_at": "2026-04-30T20:42:00Z", + "approved_until": "2026-05-01T00:42:00Z", + "state": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImFycy0xIn0.eyJhcHByb3ZhbF9pZCI6ImFwcl8wMUhYNFk4RTJORTNZMlg3UDBLNEpFNldWSCJ9.c2lnbmF0dXJl" } } } ~~~ -### Re-evaluation After Approval +## Submission Variants {#submission-variants} -The agent re-evaluates the original tool invocation; the PDP authorizes it against the approval reference. Subsequent same-class CRM tool invocations within the approval lifetime are also authorized without a new Access Request. +### Submission with Requested Access -The re-evaluation request does not repeat the original `evaluation_id`. The PDP resolves the `approval.id` (and `approval.state`, when present) to the approved Access Request task, original denied evaluation, and approved CRM tool-class scope. +Non-normative example: ~~~ http -POST /access/v1/evaluation HTTP/1.1 +POST /access/v1/requests HTTP/1.1 Host: pdp.example.com Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA Content-Type: application/json +Idempotency-Key: 7b8d0f0d-65a1-4af1-9fd3-a684f08a5d13 { "subject": { "type": "user", - "id": "alice@example.com", - "properties": { - "act": { - "iss": "https://agents.example.com", - "sub": "agent_renewal_assistant_v3", - "sub_profile": "ai_agent" - } - } + "id": "alice@example.com" }, "resource": { - "type": "tool", - "id": "crm.search_accounts" + "type": "document", + "id": "q4-plan" }, "action": { - "name": "invoke" + "name": "can_read" }, "context": { - "time": "2026-05-12T17:31:00Z", - "approval": { - "id": "apr_01HX6BCEF8K3Z2X7P0K4JE6WVK", - "approved_at": "2026-05-12T17:30:00Z", - "approved_until": "2026-05-19T17:30:00Z", - "state": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBkcC0xIn0.eyJhcHByb3ZhbF9pZCI6ImFwcl8wMUhYNkJDRUY4SzNaMlg3UDBLNEpFNldWSyIsInNjb3BlIjoiY3JtX3Rvb2xzIiwiZXhwIjoxNzc5MjEwMDAwfQ.c2lnbmF0dXJl" + "business_justification": "Needed for customer renewal review" + }, + "requested_access": { + "requested_until": "2026-05-01T00:15:00Z" + }, + "denial": { + "evaluation_id": "eval_01HX4Y2P8BQ4Y3F0V0K9D6Z7M1", + "evaluated_at": "2026-04-30T20:15:00Z", + "expires_at": "2026-04-30T20:25:00Z", + "reason": "approval_required", + "template": "manager_approval" + } +} +~~~ + +### Task Handle with Display and Links + +Non-normative example: + +~~~ http +HTTP/1.1 202 Accepted +Content-Type: application/json +Location: https://pdp.example.com/access/v1/requests/arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4 + +{ + "task": { + "id": "arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4", + "status": "pending", + "status_endpoint": "https://pdp.example.com/access/v1/requests/arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4", + "expires_at": "2026-04-30T23:00:00Z", + "links": { + "cancel": "https://pdp.example.com/access/v1/requests/arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4/cancel" + }, + "display": { + "title": "Access request submitted", + "description": "Your manager has been asked to approve access." } } } ~~~ -### Final Decision +### Synchronous Completion {#synchronous-submission-example} + +Non-normative synchronous-completion example, where policy auto-approved the request: ~~~ http -HTTP/1.1 200 OK +HTTP/1.1 201 Created Content-Type: application/json +Location: https://pdp.example.com/access/v1/requests/arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V5 { - "decision": true, - "context": { + "task": { + "id": "arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V5", + "status": "approved", + "status_endpoint": "https://pdp.example.com/access/v1/requests/arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V5" + }, + "result": { + "mode": "reevaluate", "approval": { - "id": "apr_01HX6BCEF8K3Z2X7P0K4JE6WVK", - "approved_until": "2026-05-19T17:30:00Z" + "id": "apr_01HX4Y8E2NE3Y2X7P0K4JE6WVJ", + "approved_until": "2026-05-01T00:42:00Z" } } } ~~~ -# Implementation Considerations {#impl-considerations} +# Motivation and Use Cases -This appendix describes common deployment patterns and is non-normative. +This non-normative appendix describes the use cases behind the profile and the goals it was written against. -## Identity Governance and Approval Platforms +Authority may need to change during execution: -Many implementations sit on top of an existing identity-governance, ITSM, or approval platform that already has catalogs, policy engines, approval workflows, and provisioning pipelines. A useful mapping pattern is: +* An AI agent discovers documents, records, or channels it needs mid-task, potentially producing many access denials. A broad-scope approval covers a class of later invocations; the agent runtime persists the Task Handle and resumes on completion ({{CALLBACK}}, {{ACTOR}}). +* An OAuth Authorization Server receives a combination of requested scopes that requires policy, risk, or human review before token issuance. The approval is consumed at issuance time by a companion profile that binds it to the issued token. +* An API gateway encounters an operation beyond a user's standing role and routes it to an owner for approval. This is the single-item flow of {{protocol-overview}} with no companion. +* A Security Token Service discovers that a downstream resource requires per-call approval beyond the upstream token's authority. The approval is short-lived and re-evaluated on each call. -* The platform itself acts as the Access Request Service. Its task or request entity becomes the Task Handle and its approval workflow runs unchanged. -* A thin AuthZEN Policy Decision Point is deployed alongside the platform. It produces AuthZEN Authorization API evaluations from current platform state and emits requestable denials when access is missing and an approval workflow exists for it. -* The Policy Enforcement Point is either an enforcing application (reactive: a gateway calls the AuthZEN Authorization API when a user attempts an operation) or a request user interface or agent (proactive: the user opens a request portal). Both are valid PEPs under this profile. +These denials can lead to workflows that grant new authority. A machine-readable handoff serves autonomous callers without a human present, and replaces custom approval prompts, out-of-band tickets, and vendor-specific integrations in user-facing applications. -Re-evaluation Mode aligns directly with this pattern: provisioning changes platform state, and a subsequent AuthZEN Authorization API evaluation reflects that state. Implementations mapping their richer task lifecycle states onto the canonical statuses defined in this profile SHOULD follow the guidance in {{status-mapping}}. +The profile is for resolution that requires an approval, a grant, or another action that produces authority the caller does not hold. A denial that could be completed with attributes the caller already holds, or with a residual the caller can evaluate locally, is generally better represented as missing information or partial evaluation, though a deployment may still route it through an approval workflow ({{protocol-overview}} and the Introduction state the boundary). Within missing authority, the portable approval-binding form is particularly useful where the PDP can neither see the approval in its own state nor take it as a durable fact; a single task that fans out across trust domains produces that shape ({{why-not-a-remediation-url}}). -## Form and Catalog Translation +Three goals shaped the design beyond the protocol invariants stated in the Introduction: -Most existing platforms have proprietary form description languages with field types beyond JSON Schema's native vocabulary, and proprietary catalog APIs with vendor-specific request and response shapes. Implementations translate to the JSON Schema referenced by `request_schema_url` and to the Catalogs Document and Catalog Endpoint protocol defined in {{catalog-references}}. Translation may be lossy for vendor-specific widgets and metadata; richer rendering details belong behind `form_url`, while the JSON Schema and Catalogs Document provide enough information for an autonomous PEP. Deployments that expose tools or catalogs to autonomous agents through an agent protocol can additionally surface catalogs through that protocol; see {{catalog-agent-protocol}}. +* A common handoff to human, automated, or hybrid evaluators that leaves existing approval infrastructure in place. +* High-volume callers absorbed by workflow patterns, such as broad-scope, auto-, pre-, and bulk approval, rather than per-denial human review. +* Requestability that is machine-readable, so an autonomous PEP can construct a conformant submission without human input. -## Notification Channels +# Implementation Considerations {#impl-considerations} -Implementations frequently already have webhook subscriptions or other deployment-level event channels. Per-task callbacks ({{callback-completion}}) are an alternative; deployments may rely on existing webhook infrastructure or event-streaming bindings defined by companion specifications for completion notification. +This appendix describes common deployment patterns and is non-normative. -## Time and Clock Skew +## Identity Governance and Approval Platforms -This profile uses {{RFC3339}} timestamps in multiple places: `context.evaluated_at`, `context.access_request.expires_at`, `denial.expires_at`, `task.expires_at`, `approval.approved_at`, and `approval.approved_until`. Each timestamp is produced on one host (PDP, Access Request Service, or PEP) and may be compared against a clock on another host, so clock skew between hosts can produce incorrect freshness or expiry decisions. +An identity-governance, ITSM, or approval platform can implement the roles as follows: -Implementations SHOULD allow a small skew tolerance when comparing a remote-host timestamp against the local clock. A tolerance of 30 seconds is typical; tolerances above 60 seconds are NOT RECOMMENDED. A PEP comparing `approval.approved_until` to local time MAY treat the approval as valid until `approved_until` plus the tolerance. An Access Request Service comparing `denial.expires_at` (the PEP-echoed requestable-denial hint expiry) to its local clock MAY accept submissions arriving up to the tolerance after that timestamp, after verifying the echoed value against the denial-binding material. +* The platform acts as the Access Request Service, exposing its task or request as a Task Handle while retaining its approval workflow. +* The PDP evaluates platform state and emits requestable denials when access is missing and an approval workflow exists. +* The PEP enforces access in an application, request interface, or agent, either reacting to an attempted operation or evaluating proactively, as in a request portal. -Hosts that produce timestamps SHOULD synchronize their clocks against a reliable time source (for example, NTP or PTP) to keep skew well below the tolerance window. Deployments with stricter requirements (for example, regulatory or audit constraints) MAY define a tighter tolerance and document it as part of their deployment profile. +Provisioning changes platform state; re-evaluation reads that state. Implementations mapping their richer task lifecycle states onto the canonical statuses defined in this profile SHOULD follow the guidance in {{status-mapping}}. + +## Form Translation + +When translating proprietary forms, distinguish submission data from vendor-specific widgets and metadata. `request_schema_url` describes the input an autonomous PEP needs; `form_url` preserves richer rendering. The AuthZEN Access Request Catalog Profile {{CATALOG}} handles fields whose values come from catalog APIs. ## Evaluators and Workflow Design -The Access Request Service determines what kind of evaluator processes a given submission. Evaluators commonly include: +The Access Request Service selects evaluators for each submission, such as: + +* Human approvers: owners, managers, security reviewers, or delegates acting through a user interface. +* Policy engines: static or dynamic rules for ownership, separation of duties, conflicts of interest, or organizational policy. +* Risk engines: runtime signals scored against approval, denial, or escalation thresholds. +* AI supervisors: evaluators that summarize requested authority, assess stated intent, and approve, deny, or hand off. +* Hybrid pipelines: combinations such as risk checks that escalate non-trivial cases to a human. + +Deployments can combine evaluators freely. The same completion, notification, re-evaluation, and approver-eligibility rules ({{approver-eligibility}}) apply to human and automated evaluators. -* Human approvers acting through a user interface (an owner, manager, security reviewer, or delegate). -* Automated policy engines that apply static or dynamic rules (separation-of-duties checks, ownership rules, conflict-of-interest constraints, organizational policy). -* Risk engines that score the request against runtime signals and approve, deny, or escalate based on score thresholds. -* AI supervisors or LLM-based evaluators that summarize requested authority, reason about stated intent, and approve, deny, or hand off to another evaluator. -* Hybrid pipelines that combine these (for example, an automated risk check that escalates to a human reviewer only for non-trivial cases). +To avoid overwhelming human reviewers, high-volume deployments can use: -This profile does not constrain which evaluator a deployment uses or how evaluators are combined. An Access Request can be resolved entirely without human involvement, entirely by a human approver, or by any mixture. The protocol's synchronous-completion response, callback notification, and re-evaluation semantics apply uniformly across evaluator types. The approver-eligibility, self-approval, and separation-of-duties requirements of {{approver-eligibility}} apply to whichever entity completes a workflow step, whether human or automated. +* Auto-approval: resolve low-risk requests synchronously with `201 Created` and a populated `result`, without human review ({{access-request-response}}). +* Broad-scope approval: approve a class of operations, such as "agent X may call tool Y for 30 days." Subsequent submissions can auto-approve or become unnecessary because re-evaluation permits access. +* Bulk approval: act on related submissions in one workflow step. +* Pre-approval or standing grants: establish authority out of band, such as at agent provisioning, to avoid denials requiring interactive review. -When the calling population includes high-volume PEPs (gateways aggregating many users, OAuth Authorization Servers serving fleets of clients, or autonomous agents that discover and request many fine-grained permissions), the protocol's per-denial submission shape is sufficient on the wire but must be paired with workflow design that does not require interactive human review for every submission. Without such design, evaluation volume overwhelms human reviewers and the deployment is unusable at scale. +Bulk submission, idempotency, synchronous completion, and approval expiry support these workflows. -Common workflow patterns that absorb volume include: +## Mapping Backend States {#status-mapping} -* Auto-approval rules that resolve low-risk requests synchronously, returning `201 Created` with a populated `result` and never engaging a human reviewer (see {{access-request-response}}). -* Broad-scope approvals that grant a class of future evaluations from a single decision (for example, "approve agent X to call tool Y for the next 30 days"), so subsequent same-class submissions either auto-approve or are unnecessary because re-evaluation already permits the access. -* Bulk approval, where an evaluator acts on a batch of related submissions in a single workflow step. -* Pre-approval or standing grants established out of band (for example, when an agent is provisioned), so the caller never reaches a denial that requires interactive approval. +Implementations map backend lifecycle states to the canonical Task Status Endpoint values ({{task-status}}). This non-normative table gives a starting point: -This profile defines the substrate; it does not define approval workflow. The Access Request Service is responsible for implementing the evaluator policy and the workflow primitives that route submissions appropriately. The protocol's bulk submission, idempotency, synchronous-completion response, and approval-expiry semantics provide the inputs an Access Request Service needs to apply these patterns. +| Backend state | Canonical status | +|---|---| +| Open, awaiting approval or processing | `pending` | +| Closed, all required approval steps satisfied | `approved` | +| Closed, an approval step rejected the request | `denied` | +| Closed, time-bounded request elapsed before completion | `expired` | +| Closed, requester or administrator stopped the request | `cancelled` | +| Closed, system error prevented completion | `failed` | # Design Rationale {#design-rationale} -This appendix records non-obvious design choices and the reasoning behind them. It is non-normative. Where the spec elsewhere defines a normative rule, that rule governs; this appendix only explains why the rule takes the shape it does. +This non-normative appendix explains design choices, grouped by the question a reader brings; the rules in the body of the specification govern. -## Why a profile of the AuthZEN Authorization API, rather than a standalone specification? +## Scope -The AuthZEN Authorization API defines the allow/deny decision surface that protected systems already integrate with. Building a separate request-and-approval protocol would duplicate the AuthZEN Authorization API's evaluation model and split the authorization ecosystem. As a profile of the AuthZEN Authorization API, this specification reuses the AuthZEN Authorization API's Subject, Resource, Action, Context, and Decision concepts; introduces a single new object (`context.access_request`) on the response side; and reuses the AuthZEN Authorization API's evaluation endpoint for the re-evaluation step. A PDP that already speaks the AuthZEN Authorization API gains this profile by emitting one additional object on denials and accepting one additional context member on re-evaluation requests. +### Why a protocol rather than a remediation URL? {#why-not-a-remediation-url} -## Why is Re-evaluation Mode the only base completion mode? +A denial that carries a remediation URL hands a human to a page. Where the PDP already has the approval when it next evaluates, from its own store or carried in as context, that is enough, and it crosses PEP and PDP vendors cleanly, since it is one field emitted and rendered. This profile adds what a URL does not provide: machine submission by a caller with no human present, a submission bound to the exact denied evaluation, asynchronous task state a caller can hold across restarts, a portable completion result, and an approval the PDP can verify at re-evaluation. A deployment that shares state between the PDP and the Access Request Service uses these properties with `evaluation_id` and `approval.id`; the properties matter most where the parties share no vendor, no trust domain, and no prior wiring, which three boundaries describe and one flow crosses: an agent from one vendor, inside a customer's application from a second, attempts a refund that the customer's policy routes to its own approval service from a third. -The PDP is the authoritative point at enforcement time. Returning an AuthZEN Authorization API decision (rather than a token or any other directly-enforceable artifact) ensures that current policy, subject status, risk state, revocation, and approval expiry are all evaluated again at the point of use, not frozen at approval time. Approval workflows often take minutes to days; conditions can change. Profiles that bind approval to a specific issuance flow (such as OAuth token issuance, where the issued token is itself the decision representation) define their own completion mode through the `result.mode` extension point ({{completion-semantics}}); the base profile deliberately keeps that surface profile-shaped. +* Interop. The application and the approval service are different vendors. A standing role can be provisioned across that line, but not a per-request approval bound to a specific denied action, and a URL only hands off a human. One requestable-denial contract that any conforming Access Request Service accepts replaces a connector per customer: the agent submits to a service it was never wired for, holds the Task Handle, and moves on. +* Trust. The application's PDP shares no approval-record state with the service that granted the approval. When a human approves, the service returns an Approval Result whose `approval.state` is bound to that request, and the PDP verifies it against the service's key, which it already trusts as this customer's approver, without reading the service's store. That is issuer trust, not a shared database. Carrying a durable role as context instead would hand the agent standing privilege, which an authorize-every-action model exists to avoid. +* Semantics. The agent has no prior wiring and no human at a URL, so the denial itself carries the remediation contract: this is requestable, here is the binding material, here is where to submit and track. -## Why does the approval round-trip through the PEP rather than direct PDP-to-Access-Request-Service communication? +The requestable-denial signal is advisory, so a PEP that does not implement this profile sees a plain denial and adoption proceeds one participant at a time. -The Access Request Service and PDP may be the same component, components in the same deployment, or independent services. Routing the approval reference through the PEP makes the protocol topology-agnostic: the PEP carries `result.approval` from the Access Request Service to the PDP through a normal AuthZEN Authorization API evaluation, with no requirement for back-channel communication or shared state. Deployments where the PDP and Access Request Service share state benefit because the PDP can resolve `approval.id` directly; deployments where they are independent benefit because integrity-protected `approval.state` lets the PDP verify the approval without trusting the Access Request Service's API. +### Why reuse AuthZEN? {#why-a-profile-of-the-authzen-authorization-api-rather-than-a-standalone-specification} -## Why one Access Request Endpoint per deployment, rather than per-resource or per-tenant? +Reusing AuthZEN's evaluation model avoids a second authorization interface. The profile keeps its Subject, Resource, Action, Context, and Decision concepts, adds `context.access_request` to denials, and carries `context.approval` through the existing evaluation endpoint. -A reader familiar with REST conventions might expect resource-scoped endpoints (for example, `/resources/{id}/access-requests`) or tenant-scoped endpoints in multi-tenant SaaS. A single endpoint per deployment, identified by `access_request_endpoint` in PDP metadata, simplifies discovery: one metadata lookup, one stable call site, no URL templating in PEP code. Routing decisions (workflow class, tenant, resource family) happen inside the request payload via `template`, the submitted Subject/Resource/Action, and other context members, rather than via URL structure. Intermediate enforcers (an OAuth Authorization Server or other gateway acting as PEP) MAY proxy the endpoint and present a different URL to their own callers while preserving the protocol surface. +### Why leave workflows out of scope, and why companion profiles? {#why-does-the-spec-deliberately-not-define-a-workflow-engine-approval-policy-language-or-user-interface} -## Why are there two binding patterns (`evaluation_id` and `binding_token`)? +Standardizing the handoff, rather than the workflow, lets deployments retain their existing IGA, ITSM, chat-approval, or custom infrastructure; a common workflow language or interface would force incompatible platforms into one model. The same principle places capability that only some deployments need, and that brings its own document format, endpoint, or verification model, in a companion profile that hooks in through an extension point and registers its member names: catalogs, bulk submissions, callbacks, and actor delegation. Each evolves without revising this specification, and a core-only implementation sees defined names it does not implement rather than unknown ones. -Different deployment topologies have different trust models: +## Completion -* In a same-service or trusted deployment, the Access Request Service can look up the denied evaluation by `evaluation_id` in shared or accessible state. No cryptographic verification is needed at the boundary. -* In a deployment where the Access Request Service is independent of the PDP, the Access Request Service cannot trust the PEP's claim that a denial happened. A PDP-signed `binding_token` lets the Access Request Service verify the denial cryptographically without a back-channel. +### Why re-evaluate after approval? {#why-is-re-evaluation-mode-the-only-base-completion-mode} -Supporting both patterns lets the same wire format work across topologies without forcing every deployment to operate signing infrastructure or shared state. +Approval can take minutes or days; policy, subject status, risk, and approval validity can change meanwhile. A new PDP decision checks those conditions at use, rather than freezing them at approval. -## Why does the submission's `denial` object carry only key fields, not the full AuthZEN Decision? +### Why carry approval through the PEP? {#why-does-the-approval-round-trip-through-the-pep-rather-than-direct-pdp-to-access-request-service-communication} -A reader expecting an audit-style echo of the denied Decision might wonder why the submission carries `evaluation_id`, `evaluated_at`, `expires_at`, `reason`, `binding_token`, and `template` rather than the entire `{decision, context}` object. Two reasons. First, the binding material the Access Request Service consumes (`evaluation_id` and `binding_token`) provides stronger evidence of the denial than a verbatim JSON echo could, since binding material is signed or server-resolvable and an echo would be PEP-supplied. Second, the other fields of `context.access_request` (`endpoint`, `display`, `form_url`, etc.) are evaluation-time PEP guidance, not data the Access Request Service consumes at submission time. Carrying only what the Access Request Service uses keeps the wire surface small. +Carrying `result.approval` through a normal evaluation avoids requiring a back channel or shared state. A PDP with access to trusted state resolves `approval.id`; an independent PDP verifies integrity-protected `approval.state`. The PEP uses the same wire shape in either topology. -## Why does the `denial` object support both a top-level and per-item form for bulk submissions? +### Why allow other completion modes? {#why-is-resultmode-extensible-at-all-given-the-base-defines-only-one-mode} -A reader looking at the bulk submission shape sees a top-level `denial` object alongside per-item `denial` members inside `items[]` and might wonder why both exist. Real bulk submissions come in two shapes. In the first, a single batch evaluation produces one denial that covers multiple (Resource, Action) pairs; the top-level `denial` carries one set of binding material whose JWS payload or `evaluation_id` claims encompass the whole bundle. In the second, multiple separate evaluations produce distinct denials that the PEP bundles into one submission; each item carries its own per-item `denial` with binding material specific to that item. Supporting both shapes lets the wire format absorb both batch-evaluation and bundled-from-separate-evaluations patterns without forcing the PEP to either issue separate Access Requests (losing the bulk benefit) or fabricate a synthetic bundle binding (compromising binding integrity). +Token-issuance, credential-issuance, and direct-decision flows may consume approval without re-evaluation. The `result.mode` extension point lets profiles define those flows without changing the base wire shape or its PDP-authoritative completion mode. -## Why is `approval.state` distinct from `binding_token` when both are opaque round-trip slots? +## Binding -The two slots play different protocol roles with different constraint regimes. `binding_token` is PDP-issued and Access-Request-Service-verified; it MUST be integrity-protected, typically as a JWS, with the token-hygiene claim recommendations in {{binding-token-integrity}}. `approval.state` is Access-Request-Service-issued (or PDP-issued via the Access Request Service) and PDP-verified; it is opaque and format-flexible, allowing a signed token, a lookup reference, or deployment-specific state. The different names signal the asymmetric constraint regimes; a unified name would over-promise that the two slots play the same role. +### Why two boundaries and two forms? {#why-are-there-two-binding-patterns-evaluationid-and-bindingtoken} -## Why does the `approval` object always carry `id`, even when `approval.state` is signed? +Binding material crosses two boundaries in opposite directions, and each boundary has a form for each trust arrangement. On the denial side, an Access Request Service with shared or accessible state looks up the denied evaluation by `evaluation_id` without cryptographic verification; without that state, a PDP-signed `binding_token` proves the denial without trusting the PEP's assertion. On the approval side, a PDP with trusted state resolves `approval.id`; without it, the service-issued `approval.state` proves the approval. The two artifacts keep separate names because their issuers, verifiers, and constraints differ: `binding_token` is PDP-issued and service-verified with a recommended claim set, while `approval.state` is service-issued and PDP-verified and may carry a signed token, a lookup reference, or deployment-specific state. Neither signing infrastructure nor shared state is forced on every deployment. -In the bound-reference pattern a signed `approval.state` already carries the approval identifier, so the top-level `approval.id` can look redundant. It is kept REQUIRED for two reasons. First, it is the stable, uniform audit and correlation handle present in both topologies: in the server-side-lookup pattern it is the resolver key, and in the bound-reference pattern it lets logs, callbacks, and task records reference the approval without parsing `approval.state`. Second, when both are present the PDP cross-checks that the identifier bound inside `approval.state` matches `approval.id`, a cheap defense against a PEP pairing a valid signed state with a mismatched identifier. A single always-present identifier keeps the wire shape uniform across topologies. +### Why require `approval.id` with signed state? {#why-does-the-approval-object-always-carry-id-even-when-approvalstate-is-signed} -## Why are timestamps always absolute, never relative durations? +`approval.id` is always present as a uniform correlation handle: a lookup key in shared-state deployments and an identifier for logs, callbacks, and tasks without parsing signed state. When signed state carries an identifier, the PDP cross-checks it against `approval.id` to detect mismatched pairs. -Absolute RFC 3339 timestamps appear at every time-bounded value in the spec: `task.expires_at`, `approved_until`, `approved_at`, `evaluated_at`, `context.access_request.expires_at`, `denial.expires_at`, `requested_access.requested_until`. Some specifications use relative durations (`expires_in`, OAuth-style) alongside absolute timestamps; this profile uses absolute timestamps throughout because two forms for the same concept create reconciliation logic at every consumer and a precedence rule at the wire. Clock skew between hosts is addressed by tolerance guidance in {{impl-considerations}}. +### Why echo selected denial fields? {#why-does-the-submissions-denial-object-carry-only-key-fields-not-the-full-authzen-decision} -## Why is `template` an opaque free-form string rather than a constrained enumeration? +Signed or server-resolvable binding material is stronger evidence than a PEP-supplied JSON echo. Other denial members, such as `endpoint`, `display`, and `form_url`, guide the PEP rather than the Access Request Service. The submission carries only the fields the service uses. -Workflow categorization is deployment-specific. An IGA platform's workflow names, an ITSM ticket-class identifier, an AI-supervisor source code, and a custom governance system's policy identifier all play the same role. Constraining `template` to an enumeration would either pick winners or grow indefinitely; leaving it opaque lets profiles register their own well-known values without revising the base. The Overbroad Approval rule ({{overbroad-approval}}) ensures `template` is treated as routing input, not as authorization policy. +## Wire details -## Why does the spec deliberately not define a workflow engine, approval policy language, or user interface? +### Why discover one Access Request Endpoint? {#why-one-access-request-endpoint-per-deployment-rather-than-per-resource-or-per-tenant} -These exist in many incompatible forms across IGA, ITSM, governance, chat-approval, and custom platforms. Standardizing them in this profile would either pick a single vendor model or define a surface so broad it carries no semantic value. The protocol layer between authorization enforcement and whatever workflow runs underneath is the interoperable seam; everything below it is implementation choice. This positioning is what lets deployments adopt the profile alongside existing approval infrastructure without rewriting the workflow. +A metadata-discovered endpoint avoids resource- or tenant-specific URL construction in the PEP. The payload's `template`, Subject, Resource, Action, and Context support routing by workflow, tenant, or resource family. An intermediate enforcer, such as an OAuth Authorization Server or gateway acting as PEP, can proxy the endpoint and present a different URL to its own callers while preserving the protocol surface. -## Why is `result.mode` extensible at all, given the base defines only one mode? +### Why use absolute timestamps? {#why-are-timestamps-always-absolute-never-relative-durations} -The base profile is opinionated about PDP-authoritative-at-enforcement (Re-evaluation Mode), but real deployments include token-issuance flows (OAuth, OAuth Transaction Authorization Challenge), credential-issuance flows, and direct-decision flows where the Access Request Service's intent is consumed without a re-evaluation step. Defining a base extension point lets profiles bind to those flows without changing the base wire shape, and lets the base spec remain stable as profile work evolves. +Absolute RFC 3339 timestamps give consumers a common deadline. Offering both absolute and relative expiry would require reconciliation and precedence rules. {{time-and-clock-skew}} addresses clock-skew tolerance. + +### Why leave `template` unconstrained? {#why-is-template-an-opaque-free-form-string-rather-than-a-constrained-enumeration} + +Workflow categories vary by deployment. An opaque `template` can map to a stable workflow, ticket class, schema, policy, source-code identifier, or profile-defined value without revising this specification. It remains routing input, not authorization policy ({{overbroad-approval}}). # Acknowledgements @@ -2109,4 +2098,12 @@ The author thanks the OpenID AuthZEN Working Group for discussion and review. -00 * Initial version (draft-mcguinness-authzen-access-request) - + +-01 + +* Protocol restructuring and modularization, with each requirement's force preserved. The core protocol (requestable denial, submission, task status, approval and re-evaluation) is presented first, followed by binding and verification rules that place signed mechanisms beside their shared-state alternatives; cancellation, delegation and acting parties, and machine-readable forms follow in their own sections; security considerations name threats and point to the rules that sit beside the mechanisms they protect. Requirements were relocated and restated without change in force, duplicated restatements were consolidated, and the conformance surface of this document changed only by what moved to companion profiles. +* Catalog references moved to the companion AuthZEN Access Request Catalog Profile. +* Bulk submission and callback notification requirements, feature-specific security considerations, and examples moved to companion profiles, with the core retaining normative references to their defined members and processing rules. +* Progressive layout: trusted-state examples lead the common protocol; artifact processing and additional mechanisms follow. The interoperability baseline and all processing requirements retain their applicability and force. A non-normative deadline guide and trusted-state walkthrough accompany the exchange, and two example omissions are corrected. +* One normative home per rule: message definitions stay in the flow sections, cut to name, presence, type, and one sentence; every processing rule sits in PEP Processing, PDP Processing, or Access Request Service Processing; restating tables and duplicate examples removed; fourteen duplicate statements folded into their surviving rules. +* Actor delegation (the `client.actor` and `client.source` members and actor-chain verification) moved to the companion AuthZEN Actor Delegation Profile, with the core retaining the preservation, comparison, and authorization-input rules that reference those members. \ No newline at end of file diff --git a/profiles/authzen-access-request-approval/authzen-access-request-bulk-profile-1_0.md b/profiles/authzen-access-request-approval/authzen-access-request-bulk-profile-1_0.md new file mode 100644 index 00000000..d107d70d --- /dev/null +++ b/profiles/authzen-access-request-approval/authzen-access-request-bulk-profile-1_0.md @@ -0,0 +1,221 @@ +--- +title: "AuthZEN Bulk Access Requests Profile - Draft 1" +abbrev: "ARAP Bulk" +category: std +ipr: none + +docname: authzen-access-request-bulk-profile-1_0 +workgroup: OpenID AuthZEN +consensus: true +v: 3 +stand_alone: true +pi: [toc, sortrefs, symrefs, private] +keyword: + - authorization + - access request + - approval workflow + +author: + - + name: Karl McGuinness + org: Independent + email: public@karlmcguinness.com + +normative: + RFC8785: + ARAP: + title: "AuthZEN Access Request and Approval Profile 1.0" + 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 profile defines bulk Access Requests for the AuthZEN Access Request and Approval Profile: multiple requested items, bundle and per-item denial binding, aggregate task status, and per-item approval handling. + +--- middle + +# Introduction + +This companion to the AuthZEN Access Request and Approval Profile {{ARAP}} defines submission of multiple Resource/Action items in one Access Request. It defines bundle and per-item denial binding, per-item outcomes, aggregate task status, and bulk cancellation and re-evaluation. + +The profile defines the `items` member of the Access Request submission, the `items` member of the Task Handle, and the `partial` task status. The base profile's authentication, authorization, freshness, task handling, and approval verification rules continue to apply; this document specifies the bulk variations. An aggregate result is not a grant of access to every item. + +# Requirements Notation and Conventions + +{::boilerplate bcp14-tagged} + +The terms PEP, PDP, Subject, Resource, Action, Context, Access Request, Access Request Service, Task Handle, and Approval Result are used as defined by {{ARAP}}. + +# Bulk Submissions {#bulk-submissions} + +## Bulk Request Body {#bulk-request-body} + +Bulk submissions use the top-level members defined in [Request Body](https://openid.github.io/authzen/authzen-access-request-approval-profile-1_0.html#submission-request-body) and [Additional Request Members](https://openid.github.io/authzen/authzen-access-request-approval-profile-1_0.html#submission-additional-information), with these changes: + +* When `items` is present, the top-level `resource` MUST be omitted. +* When `items` is present, the top-level `action` MUST be omitted. +* The top-level `denial` is REQUIRED when any item lacks a per-item `denial`. It is a bundle denial; its coverage rules are in {{bulk-denial-binding}}. +* The top-level `denial` is OPTIONAL when every item carries its own per-item `denial`. + +The Idempotency-Key covers the entire submission body, including all members of the `items` array when present. + +## Request Items + +`items`: +: OPTIONAL. Array. Multiple `(resource, action)` items submitted as a single bundled Access Request. When present, `resource` and `action` MUST be omitted at the top level. Each item is an object with the following members: + + * `resource`: REQUIRED. The AuthZEN Resource for this item. + * `action`: REQUIRED. The AuthZEN Action for this item. + * `requested_access`: OPTIONAL. Per-item `requested_access` overrides; merged with the top-level `requested_access` with item values taking precedence. + * `denial`: OPTIONAL. Per-item denial binding when items came from separate AuthZEN Authorization API evaluations. A per-item `denial` uses the same members as the top-level `denial` object. See [The denial Object](https://openid.github.io/authzen/authzen-access-request-approval-profile-1_0.html#submission-denial-object) for denial members and {{bulk-denial-binding}} for bulk coverage rules. + +Non-normative bulk-submission example: + +~~~ http +POST /access/v1/requests HTTP/1.1 +Host: pdp.example.com +Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA +Content-Type: application/json +Idempotency-Key: 7b8d0f0d-65a1-4af1-9fd3-a684f08a5d14 + +{ + "subject": { + "type": "user", + "id": "alice@example.com" + }, + "items": [ + { + "resource": {"type": "document", "id": "q4-plan"}, + "action": {"name": "can_read"} + }, + { + "resource": {"type": "channel", "id": "engineering"}, + "action": {"name": "can_post"} + } + ], + "context": { + "business_justification": "Onboarding to the renewal review project" + }, + "requested_access": { + "requested_until": "2026-05-14T20:15:00Z" + }, + "denial": { + "evaluation_id": "eval_01HX4Y2P8BQ4Y3F0V0K9D6Z7M2", + "evaluated_at": "2026-04-30T20:15:00Z", + "expires_at": "2026-04-30T20:25:00Z", + "reason": "approval_required", + "binding_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBkcC0xIn0.eyJidW5kbGVfaWQiOiJidW5fMDFIWDVTVUJNMSIsIml0ZW1zIjpbeyJyZXNvdXJjZSI6ImRvY3VtZW50OnE0LXBsYW4iLCJhY3Rpb24iOiJjYW5fcmVhZCJ9LHsicmVzb3VyY2UiOiJjaGFubmVsOmVuZ2luZWVyaW5nIiwiYWN0aW9uIjoiY2FuX3Bvc3QifV19.bXBfc2lnbmF0dXJl", + "template": "onboarding_bundle" + } +} +~~~ + +## Denial Binding for Bulk Submissions {#bulk-denial-binding} + +When `items` is present and any item lacks a per-item `denial`, the top-level `denial` is a bundle denial whose verifiable binding material MUST cover the Subject, authorization-relevant Context, and every Resource and Action in `items`. + +The top-level `denial` presence rules are in {{bulk-request-body}}; the binding claims are defined in [Denial Binding Claims](https://openid.github.io/authzen/authzen-access-request-approval-profile-1_0.html#binding-token-integrity). For bulk submissions, those claims cover the entire `items` array and authorization-relevant Context: + +* Inline bulk binding claims list each submitted item, including the full Resource and Action objects for that item, in the same order as the bound Access Request. +* A bulk `binding_hash` is the base64url-encoded (without padding) SHA-256 digest of the JCS {{RFC8785}} serialization of the JSON object `{"subject": , "items": [{"resource": , "action": }, ...], "context": }`, where `` is the bound Subject with `subject.properties.act` removed and the `items` array order is the order bound by the denial. Implementations that use a bulk hashed form MUST use exactly this construction. + +When every item carries its own per-item `denial`, each per-item binding is verified using the single-item rules instead of this bundle construction. + +## Response Items and Aggregation {#bulk-aggregation} + +`items`: +: REQUIRED when the original submission carried an `items` array; otherwise OPTIONAL. Array. Per-item progress for bundled Access Requests. Each element corresponds positionally to the submission's `items` member and has the following members: + + * `resource`: REQUIRED. The AuthZEN Resource for this item, echoing the submission. + * `action`: REQUIRED. The AuthZEN Action for this item. + * `status`: REQUIRED. Per-item status using the values defined in [Task Status and Transitions](https://openid.github.io/authzen/authzen-access-request-approval-profile-1_0.html#task-status). + * `result`: OPTIONAL before the item reaches a terminal status; REQUIRED when the item status is `approved`. Per-item completion result with the same shape as the top-level `result` ([Approval and Re-evaluation](https://openid.github.io/authzen/authzen-access-request-approval-profile-1_0.html#completion-semantics)). + +When `items` is present, `progress` describes aggregate workflow progress for the bundled task; per-item progress is tracked in `task.items[]`. + +When `task.status` is `approved` and the task contains an `items` array, each approved item in `task.items[]` MUST include its own `result` object. The response MAY also include a top-level `result` object for aggregate workflow information, but a PEP MUST NOT use that top-level `result` to authorize an individual item unless the same result is also present in that item's `result` member. + +When the `items` member is present, the aggregate `task.status` is computed from per-item statuses as follows: + +* If any item is `pending` or in an implementation-defined non-terminal status ([Task Status and Transitions](https://openid.github.io/authzen/authzen-access-request-approval-profile-1_0.html#task-status)), the aggregate is `pending`. +* Otherwise, if all items share the same terminal status, the aggregate is that status. +* Otherwise, with two or more distinct terminal statuses present across items, the aggregate is `partial`. + +A PEP processing a bundled task MUST consult `task.items[].status` and `task.items[].result` to determine per-item outcomes; the PEP MUST NOT infer per-item outcomes from the aggregate `task.status` alone. A top-level `result` MUST NOT be used to authorize any individual item in a bundled task unless the same result is also present in that item's `result` member. + +## Bulk Task Status {#bulk-task-status} + +`partial`: +: All items in a bulk task ({{bulk-submissions}}) reached terminal status, but with mixed outcomes (for example, some items approved while others denied). This status is only valid for tasks containing an `items` array. + + * A PEP receiving `partial` MUST consult `task.items[].status` to determine per-item outcomes. + * A PEP receiving `partial` MUST NOT infer aggregate access permission. + +## Bulk Status, Cancellation, and Re-evaluation + +Each item follows the base state machine independently. The aggregate `task.status` follows {{bulk-aggregation}}, reaching `partial` when all items reach terminal status with two or more distinct terminal statuses present. + +Cancellation cancels every `pending` item and leaves terminal items unchanged. Behavior for implementation-defined non-terminal statuses is implementation-defined; an Access Request Service that defines additional non-terminal statuses SHOULD document whether cancellation transitions those items to `cancelled` or leaves them unchanged. + +The aggregate status is then recomputed: `cancelled` when no item completed before cancellation, or `partial` when some items reached other terminal statuses first. If every item was already terminal, cancellation returns `409 Conflict` with `urn:openid:authzen:access-request:error:invalid_task_state`. + +For a task containing an `items` array, each approved item MUST include a per-item `result` that is independently enforceable according to its own `result.mode`. + +For a bundled Access Request, the default approval scope for each approved item is that item's Subject, Resource, Action, and relevant Context. + +When the original submission carried an `items` array, the PEP re-evaluates each approved item separately, including that item's `result.approval` at `context.approval` in the item's re-evaluation request as described in [Approval and Re-evaluation](https://openid.github.io/authzen/authzen-access-request-approval-profile-1_0.html#completion-semantics). This profile does not define an aggregate re-evaluation that covers multiple items in one AuthZEN Authorization API call. + +# Security Considerations + +**Bulk bundle escalation.** An aggregate status or result could be mistaken for approval of every item. {{bulk-submissions}} requires per-item status and results and limits use of a top-level result for item authorization. + +**Bundle substitution and reordering.** Denial binding covers item contents and order ({{bulk-denial-binding}}). The base profile's binding, authentication, and authorization requirements apply to every requested item. + +# Privacy Considerations + +The privacy considerations of {{ARAP}} apply. Bundles can disclose relationships among resources and approvals; per-item outcomes can reveal different policy or workflow decisions. + +# IANA Considerations + +This specification makes no requests of IANA. + +# OpenID Foundation Registry Considerations + +## AuthZEN Access Request Member Names Registry {#member-names} + +This specification registers the following entries in the AuthZEN Access Request Member Names registry established by {{ARAP}}. + +| Name | Extension Point | Description | +|---|---|---| +| `items` | Access Request submission | Array of requested Resource/Action items and optional per-item denial and request information. | +| `items` | Task Handle | Per-item status and completion results for a bundled request. | + +Change Controller for all entries: OpenID Foundation AuthZEN Working Group. Specification Document for all entries: This document. + +The `partial` task status extends the status enumeration of {{ARAP}}; that enumeration has no separate registry. + +--- back + +# Implementation Considerations + +The following non-normative mapping extends the base profile's backend-state mapping: + +| Backend state | ARAP task status | +|---|---| +| Closed, items in a bulk task reached two or more distinct terminal statuses | `partial` | + +# Design Rationale + +## Why support bundle and per-item denials? {#why-does-the-denial-object-support-both-a-top-level-and-per-item-form-for-bulk-submissions} + +A batch evaluation can produce one denial covering multiple Resource/Action pairs; separate evaluations produce separate denials. Top-level binding supports the first case, and per-item binding the second. Both allow a bundled submission without requiring the PEP to fabricate a bundle binding or submit each request separately. + +# Document History + +-00 + +* Extracted bulk Access Request requirements and related material from the AuthZEN Access Request and Approval Profile without changing their force or conditions. diff --git a/profiles/authzen-access-request-approval/authzen-access-request-callback-profile-1_0.md b/profiles/authzen-access-request-approval/authzen-access-request-callback-profile-1_0.md new file mode 100644 index 00000000..5623c49e --- /dev/null +++ b/profiles/authzen-access-request-approval/authzen-access-request-callback-profile-1_0.md @@ -0,0 +1,403 @@ +--- +title: "AuthZEN Callback Notifications Profile - Draft 1" +abbrev: "ARAP Callback" +category: std +ipr: none + +docname: authzen-access-request-callback-profile-1_0 +workgroup: OpenID AuthZEN +consensus: true +v: 3 +stand_alone: true +pi: [toc, sortrefs, symrefs, private] +keyword: + - authorization + - access request + - approval workflow + +author: + - + name: Karl McGuinness + org: Independent + email: public@karlmcguinness.com + +normative: + RFC6750: + ARAP: + title: "AuthZEN Access Request and Approval Profile 1.0" + target: "https://openid.github.io/authzen/authzen-access-request-approval-profile-1_0.html" + author: + - + ins: K. McGuinness + name: Karl McGuinness + date: 2026 + BULK: + title: "AuthZEN Bulk Access Requests Profile 1.0" + target: "https://openid.github.io/authzen/authzen-access-request-bulk-profile-1_0.html" + author: + - + ins: K. McGuinness + name: Karl McGuinness + date: 2026 + +--- abstract + +This profile defines callback notifications for the AuthZEN Access Request and Approval Profile, including the callback submission member, notification payload, endpoint validation, authentication, and interaction with polling and deployment-level subscriptions. + +--- middle + +# Introduction + +This companion to the AuthZEN Access Request and Approval Profile {{ARAP}} defines the `callback` submission member and authenticated notifications of task completion. It also describes completion through deployment-level event subscriptions. Task status retrieval, approval verification, and re-evaluation remain defined by the base profile. + +A notification can carry an enforceable result or prompt the PEP to retrieve task status. The delivery, authentication, and correlation rules below distinguish those cases. This profile defines no new capability-negotiation or callback-acceptance handshake. + +The `partial` event name corresponds to the bulk task status defined by {{BULK}}; other task statuses are defined by {{ARAP}}. + +# Requirements Notation and Conventions + +{::boilerplate bcp14-tagged} + +The terms PEP, PDP, Subject, Resource, Action, Context, Access Request, Access Request Service, Task Handle, and Approval Result are used as defined by {{ARAP}}. + +# Callback Completion {#callback-completion} + +`callback`: +: OPTIONAL. Object describing a callback endpoint where the Access Request Service can send completion notifications. + +A PEP MAY request callback notification by including a `callback` object in the Access Request submission. + +The `callback` object has the following members: + +`endpoint`: +: REQUIRED. HTTPS URI to which the Access Request Service sends completion notifications. + + * The Access Request Service MUST validate that the endpoint is authorized for the authenticated PEP, either by matching a pre-registered callback URI or by applying an explicit deployment allowlist. + * The Access Request Service MUST reject callback endpoints that resolve to loopback, link-local, private-use, or otherwise internal network addresses unless the deployment has explicitly allowed that destination. + + In-cluster or same-trust-domain deployments allowlist specific internal destinations rather than disabling this protection against server-side request forgery. + +`state`: +: OPTIONAL. Opaque value supplied by the PEP and returned unmodified in the callback. + +`events`: +: OPTIONAL. Array of event names requested by the PEP. Defined event names are `approved`, `denied`, `expired`, `cancelled`, `failed`, and `partial`. + +## Notification Delivery {#notification-delivery} + +Callback notifications MUST contain a `task` member and MAY contain a `result` member. When present, the `result` object MUST use one of the completion forms defined in [Approval and Re-evaluation](https://openid.github.io/authzen/authzen-access-request-approval-profile-1_0.html#completion-semantics). A callback whose `task.status` is `approved` but that does not contain an enforceable `result` is only a notification; the PEP MUST retrieve the Task Status Endpoint response before enforcing access. + +The Access Request Service MUST authenticate to the callback endpoint using a mechanism agreed between the PEP and Access Request Service. This specification does not mandate a single callback authentication mechanism, but implementations SHOULD use one of the following: an OAuth 2.0 bearer token {{RFC6750}} issued to the Access Request Service, mutual TLS, or an HMAC signature over the request body using a pre-shared key. Unauthenticated callbacks MUST NOT be accepted. + +Callback delivery is a notification optimization. The Task Status Endpoint remains authoritative unless the callback contains an enforceable completion result under [Approval and Re-evaluation](https://openid.github.io/authzen/authzen-access-request-approval-profile-1_0.html#completion-semantics). + +Non-normative notification-only callback: no `result` is included, so the PEP retrieves task status before enforcing access. + +~~~ http +POST /callbacks/access-requests HTTP/1.1 +Host: pep.example.com +Authorization: Bearer mF_9.B5f-4.1JqM +Content-Type: application/json + +{ + "state": "b3Blbi1kb2N1bWVudC1mbG93", + "task": { + "id": "arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4", + "status": "approved", + "status_endpoint": "https://pdp.example.com/access/v1/requests/arq_01HX4Y3AJZ7Y56W2F9H8Q8C1V4" + } +} +~~~ + +PEPs SHOULD verify callback origin, bind callbacks to expected task identifiers and state values, and treat callbacks as notifications unless they contain an enforceable result under {{ARAP}}. + +## Polling and Event Subscriptions {#polling-and-event-subscriptions} + +PEPs subscribed to per-task callbacks ({{callback-completion}}) or to deployment-level event subscriptions MAY skip polling entirely and rely on push notification, falling back to a single status retrieval after each notification to obtain any enforceable `result`. + +Implementations MAY satisfy completion notification through deployment-level event subscriptions (for example, organization-scoped webhooks or event-streaming bindings defined by companion specifications) rather than per-task callbacks. When a deployment relies on such a subscription, the PEP MAY omit the `callback` member from the Access Request submission. Deployment-level event subscriptions deliver the same Task Handle and lifecycle information to subscribed receivers; they are a notification channel and MUST NOT be treated as enforcement unless paired with a separate enforceable result. + +The Access Request Service MAY additionally publish lifecycle events for governance, audit, and analytics consumers through deployment-level event subscriptions defined by companion specifications. Such channels are independent of the per-task callback and are not used for enforcement. + +# Security Considerations + +**Callback security.** Callbacks expose spoofing, replay, and request-forgery risks, including server-side request forgery. {{callback-completion}} defines destination validation, notification authentication, and PEP-side checks. + +The base profile's Task Handle authorization and completion rules apply to notification content. Notification delivery does not replace approval verification or re-evaluation. + +# Privacy Considerations + +The privacy considerations of {{ARAP}} apply to callback payloads. Callback destinations and deployment-level subscriptions can disclose task state and approval information to additional recipients. + +# IANA Considerations + +This specification makes no requests of IANA. + +# OpenID Foundation Registry Considerations + +## AuthZEN Access Request Member Names Registry {#member-names} + +This specification registers the following entries in the AuthZEN Access Request Member Names registry established by {{ARAP}}. + +| Name | Extension Point | Description | +|---|---|---| +| `callback` | Access Request submission | Callback destination, correlation state, and selected events. | + +Change Controller for all entries: OpenID Foundation AuthZEN Working Group. Specification Document for all entries: This document. + +--- back + +# Examples + +## End-to-End Agent Tool Discovery + +This non-normative example shows an agent requesting access to a tool discovered mid-task. A broad-scope approval covers related invocations without a new Access Request for each call. + +### Initial Evaluation Request + +The agent attempts to invoke a CRM search tool while assembling a renewal report. + +~~~ http +POST /access/v1/evaluation HTTP/1.1 +Host: pdp.example.com +Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA +Content-Type: application/json + +{ + "subject": { + "type": "user", + "id": "alice@example.com", + "properties": { + "act": { + "iss": "https://agents.example.com", + "sub": "agent_renewal_assistant_v3", + "sub_profile": "ai_agent" + } + } + }, + "resource": { + "type": "tool", + "id": "crm.search_accounts" + }, + "action": { + "name": "invoke" + }, + "context": { + "time": "2026-05-12T15:00:00Z" + } +} +~~~ + +### Requestable Denial + +The PDP returns a denial requesting broad-scope approval for the agent to call CRM tools. + +~~~ http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "decision": false, + "context": { + "evaluation_id": "eval_01HX6A9D2M7N0F4G3K2T9P1B8X", + "evaluated_at": "2026-05-12T15:00:00Z", + "reason": "agent_authority_missing", + "access_request": { + "template": "agent_tool_class_approval", + "expires_at": "2026-05-12T15:10:00Z", + "binding_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBkcC0xIn0.eyJldmFsdWF0aW9uX2lkIjoiZXZhbF8wMUhYNkE5RDJNN04wRjRHM0syVDlQMUI4WCIsImNsYXNzIjoiY3JtX3Rvb2xzIn0.aGFzaA", + "request_schema_url": "https://requests.example.com/schemas/agent_tool_class_approval.json" + } + } +} +~~~ + +### Submitting the Access Request + +The runtime supplies actor and source members to route approval to the agent's owner and record the originating session. It persists the Task Handle and continues other work while approval proceeds, resuming this operation when the callback arrives. + +~~~ http +POST /access/v1/requests HTTP/1.1 +Host: pdp.example.com +Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA +Content-Type: application/json +Idempotency-Key: 9c1f5d12-2a18-4cba-8a5e-e0e8e2b6b5c7 + +{ + "subject": { + "type": "user", + "id": "alice@example.com" + }, + "resource": { + "type": "tool", + "id": "crm.search_accounts" + }, + "action": { + "name": "invoke" + }, + "context": { + "business_justification": "Assembling Q2 renewal report for customer ACME-1042" + }, + "requested_access": { + "requested_until": "2026-05-19T15:00:00Z" + }, + "client": { + "id": "renewal_assistant", + "actor": { + "id": "agent_renewal_assistant_v3", + "issuer": "https://agents.example.com", + "type": "ai_agent" + }, + "source": { + "session_id": "session_01HX69WJ8Q0K7P4F0V0K9D6Z7N" + } + }, + "callback": { + "endpoint": "https://agents.example.com/callbacks/access-requests", + "state": "session_01HX69WJ8Q0K7P4F0V0K9D6Z7N", + "events": ["approved", "denied", "expired"] + }, + "denial": { + "evaluation_id": "eval_01HX6A9D2M7N0F4G3K2T9P1B8X", + "evaluated_at": "2026-05-12T15:00:00Z", + "expires_at": "2026-05-12T15:10:00Z", + "reason": "agent_authority_missing", + "binding_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBkcC0xIn0.eyJldmFsdWF0aW9uX2lkIjoiZXZhbF8wMUhYNkE5RDJNN04wRjRHM0syVDlQMUI4WCIsImNsYXNzIjoiY3JtX3Rvb2xzIn0.aGFzaA", + "template": "agent_tool_class_approval" + } +} +~~~ + +### Task Handle + +~~~ http +HTTP/1.1 202 Accepted +Content-Type: application/json +Location: https://pdp.example.com/access/v1/requests/arq_01HX6AAB3J7Y56W2F9H8Q8C1V7 + +{ + "task": { + "id": "arq_01HX6AAB3J7Y56W2F9H8Q8C1V7", + "status": "pending", + "status_endpoint": "https://pdp.example.com/access/v1/requests/arq_01HX6AAB3J7Y56W2F9H8Q8C1V7", + "expires_at": "2026-05-20T00:00:00Z" + } +} +~~~ + +### Approval Callback + +Hours later, after the agent's owner approves the request, the Access Request Service notifies the agent's callback endpoint. The callback is notification-only; the agent retrieves the Task Status Endpoint before enforcing access. + +~~~ http +POST /callbacks/access-requests HTTP/1.1 +Host: agents.example.com +Authorization: Bearer mF_9.B5f-4.1JqM +Content-Type: application/json + +{ + "state": "session_01HX69WJ8Q0K7P4F0V0K9D6Z7N", + "task": { + "id": "arq_01HX6AAB3J7Y56W2F9H8Q8C1V7", + "status": "approved", + "status_endpoint": "https://pdp.example.com/access/v1/requests/arq_01HX6AAB3J7Y56W2F9H8Q8C1V7" + } +} +~~~ + +### Completed Task + +The completed task provides a seven-day approval for the CRM tool class. The PDP verifies `approval.state` during re-evaluation rather than relying only on an `approval.id` lookup. + +~~~ http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "task": { + "id": "arq_01HX6AAB3J7Y56W2F9H8Q8C1V7", + "status": "approved" + }, + "result": { + "mode": "reevaluate", + "approval": { + "id": "apr_01HX6BCEF8K3Z2X7P0K4JE6WVK", + "approved_at": "2026-05-12T17:30:00Z", + "approved_until": "2026-05-19T17:30:00Z", + "state": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBkcC0xIn0.eyJhcHByb3ZhbF9pZCI6ImFwcl8wMUhYNkJDRUY4SzNaMlg3UDBLNEpFNldWSyIsInNjb3BlIjoiY3JtX3Rvb2xzIiwiZXhwIjoxNzc5MjEwMDAwfQ.c2lnbmF0dXJl" + } + } +} +~~~ + +### Re-evaluation After Approval + +The agent re-evaluates the original tool invocation; the PDP authorizes it against the approval reference. Subsequent same-class CRM tool invocations within the approval lifetime are also authorized without a new Access Request. + +The re-evaluation request does not repeat the original `evaluation_id`. The PDP resolves the `approval.id` (and `approval.state`, when present) to the approved Access Request task, original denied evaluation, and approved CRM tool-class scope. + +~~~ http +POST /access/v1/evaluation HTTP/1.1 +Host: pdp.example.com +Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA +Content-Type: application/json + +{ + "subject": { + "type": "user", + "id": "alice@example.com", + "properties": { + "act": { + "iss": "https://agents.example.com", + "sub": "agent_renewal_assistant_v3", + "sub_profile": "ai_agent" + } + } + }, + "resource": { + "type": "tool", + "id": "crm.search_accounts" + }, + "action": { + "name": "invoke" + }, + "context": { + "time": "2026-05-12T17:31:00Z", + "approval": { + "id": "apr_01HX6BCEF8K3Z2X7P0K4JE6WVK", + "approved_at": "2026-05-12T17:30:00Z", + "approved_until": "2026-05-19T17:30:00Z", + "state": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBkcC0xIn0.eyJhcHByb3ZhbF9pZCI6ImFwcl8wMUhYNkJDRUY4SzNaMlg3UDBLNEpFNldWSyIsInNjb3BlIjoiY3JtX3Rvb2xzIiwiZXhwIjoxNzc5MjEwMDAwfQ.c2lnbmF0dXJl" + } + } +} +~~~ + +### Final Decision + +~~~ http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "decision": true, + "context": { + "approval": { + "id": "apr_01HX6BCEF8K3Z2X7P0K4JE6WVK", + "approved_until": "2026-05-19T17:30:00Z" + } + } +} +~~~ + +# Implementation Considerations + +## Notification Channels + +Existing webhook subscriptions or event-streaming bindings can provide completion notification instead of per-task callbacks ({{callback-completion}}). + +# Document History + +-00 + +* Extracted callback notification requirements and related material from the AuthZEN Access Request and Approval Profile without changing their force or conditions. diff --git a/profiles/authzen-access-request-approval/authzen-access-request-catalog-profile-1_0.md b/profiles/authzen-access-request-approval/authzen-access-request-catalog-profile-1_0.md new file mode 100644 index 00000000..543d0d69 --- /dev/null +++ b/profiles/authzen-access-request-approval/authzen-access-request-catalog-profile-1_0.md @@ -0,0 +1,642 @@ +--- +title: "AuthZEN Access Request Catalog Profile - Draft 1" +abbrev: "ARAP Catalog" +category: std +ipr: none + +docname: authzen-access-request-catalog-profile-1_0 +workgroup: OpenID AuthZEN +consensus: true +v: 3 +stand_alone: true +pi: [toc, sortrefs, symrefs, private] +keyword: + - authorization + - access request + - approval workflow + - catalog + - entitlement + - AI agent + - just-in-time access + - governance + +author: + - + name: Karl McGuinness + org: Independent + email: public@karlmcguinness.com + +normative: + RFC9110: + RFC6749: + RFC6750: + RFC6901: + 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 1.0" + target: "https://openid.github.io/authzen/authzen-access-request-approval-profile-1_0.html" + author: + - + ins: K. McGuinness + name: Karl McGuinness + date: 2026 + +informative: + RFC8693: + I-D.bhutton-json-schema: + +--- abstract + +This specification defines a companion profile to the AuthZEN Access Request and Approval Profile that adds a Catalogs Document, a Catalog Endpoint protocol, and a Catalog Response format, so that a Policy Enforcement Point, whether it renders a form for a human user or acts as an autonomous agent, can resolve Access Request form fields whose values are selected from a backing catalog such as applications, entitlements, roles, or cost centers. The profile adds one member, `request_catalogs_url`, to the requestable denial, and changes nothing about Access Request submission, task handling, or re-evaluation. + +--- middle + +# Introduction + +An Access Request submitted after a requestable denial often carries more than the Subject, Resource, and Action of the denied evaluation. Deployments ask the requester which application, which entitlement, which role, or which cost center the request concerns. Those values are not free text: they are drawn from catalogs that are large, that change continuously, and that are scoped to what a particular requester is permitted to see. + +The AuthZEN Access Request and Approval Profile {{ARAP}} lets a Policy Decision Point (PDP) publish a machine-readable description of those additional submission fields through the `request_schema_url` member of a requestable denial. JSON Schema {{I-D.bhutton-json-schema}} describes the shape and constraints of the data a submission must carry, but it has no vocabulary for remote, requester-scoped enumeration. A static `enum` embedded in the schema does not work when a catalog holds tens of thousands of entries, when the entitlements that are valid depend on the application the requester selected first, or when two requesters must see different subsets of the same catalog. + +A catalog is also an authorization boundary in its own right. The set of applications, entitlements, roles, or cost centers a requester is permitted to see is itself sensitive: it discloses organizational structure, policy shape, and finance master data. A catalog surface therefore needs authentication, authorization, and scoping rules, not only a data format. + +This profile defines that surface. It adds: + +* A Catalogs Document ({{catalogs-document}}), a sibling artifact to the form schema that maps form fields to the catalogs backing them. +* A Catalog Endpoint protocol ({{catalog-endpoint}}) for searching, scoping, and paginating a catalog. +* A Catalog Response format ({{catalog-response}}) carrying Catalog Items with well-known presentation and triage metadata. +* One member, `request_catalogs_url`, of the requestable denial's `access_request` object ({{requestable-denial-extension}}). + +This document is a profile of {{ARAP}} in the sense defined by the Extensibility and Profiles section of that specification, and through it a profile of the AuthZEN Authorization API {{AuthZEN}}. It populates the `context.access_request` extension point defined by {{ARAP}} with a single member, and defines extension points of its own ({{extensibility}}). It modifies no other part of {{ARAP}}: Access Request submission, Task Handles, completion semantics, and re-evaluation are unchanged. + +This profile does not define an entitlement or catalog data model beyond the members a PEP needs to select and submit a value. It does not define provisioning or fulfillment. It does not define an agent-protocol transport or discovery mechanism. It does not define a user-interface rendering vocabulary. + +# Requirements Notation and Conventions + +{::boilerplate bcp14-tagged} + +The terms Policy Decision Point (PDP), Policy Enforcement Point (PEP), Subject, Resource, Action, Context, and Decision are used as defined by {{AuthZEN}}. + +# Terminology + +This specification uses the terms Policy Enforcement Point (PEP), Policy Decision Point (PDP), Access Request, Access Request Service, Access Request Endpoint, requestable denial, and Task Handle as defined in {{ARAP}}, and the terms Subject, Resource, Action, Context, and Decision Context as defined in {{AuthZEN}}. + +This specification defines the following additional terms. + +Form Schema: +: The machine-readable description of the augmentations a PEP adds to an Access Request submission's `context` and `requested_access` objects, referenced by the `request_schema_url` member of a requestable denial and RECOMMENDED by {{ARAP}} to be a JSON Schema {{I-D.bhutton-json-schema}} document. + +Form Data Instance: +: The JSON instance a PEP constructs to satisfy the Form Schema, and into which catalog-resolved values are placed. JSON Pointers appearing in a Catalogs Document are evaluated against this instance. + +Catalog: +: A set of selectable values backing one or more Form Schema fields, for example applications, entitlements, roles, or cost centers. + +Catalogs Document: +: A JSON document, referenced by `request_catalogs_url`, that maps Form Schema fields to the Catalog Endpoints from which their values are resolved ({{catalogs-document}}). + +Catalog Reference: +: The object within a Catalogs Document that describes how the value of a single Form Schema field is resolved. + +Catalog Endpoint: +: An HTTP endpoint that returns a paginated, authorized, requester-scoped list of Catalog Items for a Catalog ({{catalog-endpoint}}). + +Catalog Item: +: A single selectable entry returned by a Catalog Endpoint, carrying the value the PEP places into a Form Schema field along with OPTIONAL presentation and triage metadata. + +Catalog Response: +: The JSON object a Catalog Endpoint returns for a successful request ({{catalog-response}}). + +# Protocol Overview {#overview} + +1. The PEP evaluates access using the AuthZEN Access Evaluation API and receives a requestable denial as defined by {{ARAP}}. +2. The requestable denial carries both `request_schema_url` and `request_catalogs_url` in its `context.access_request` object. +3. The PEP verifies that both URLs resolve to hosts trusted under the deployment ({{trusting-catalog-urls}}), then fetches the Form Schema and the Catalogs Document. +4. For each Form Schema field named in the Catalogs Document, the PEP calls the Catalog Endpoint named by that field's Catalog Reference, supplying a search term and any scope parameters resolved from fields already populated in the Form Data Instance. +5. The PEP places the value identified by `value_path` in the chosen Catalog Item into the Form Data Instance, then repeats step 4 for any dependent field whose scope parameters have now become resolvable. +6. The PEP submits the Access Request to the Access Request Endpoint, carrying the completed Form Data Instance as the augmentations to the submission's `context` and `requested_access` objects. +7. The Access Request Service re-validates every submitted catalog identifier before accepting the submission. + +Everything after submission proceeds exactly as defined in {{ARAP}}: the Access Request Service returns a Task Handle, the PEP polls the task or receives a callback, and an approved task is enforced through a new AuthZEN Authorization API evaluation. This profile changes none of that. + +# Requestable Denial Extension {#requestable-denial-extension} + +This profile defines one additional member of the `access_request` object in the Decision Context of a requestable denial, added at the extension point defined by the Extensibility and Profiles section of {{ARAP}}. + +`request_catalogs_url`: +: OPTIONAL. HTTPS URI. URL of a Catalogs Document describing how the PEP resolves form fields whose values are selected from a backing catalog. See {{catalogs-document}}. + +PEPs interacting with deployments that do not include `request_catalogs_url` MAY omit Catalog Endpoint resolution entirely. + +The Catalogs Document is a sibling artifact to the Form Schema; it does not modify or extend the JSON Schema referenced by `request_schema_url`. A PDP MUST include `request_schema_url` when including `request_catalogs_url`. + +The following is a non-normative example: + +~~~ json +{ + "decision": false, + "context": { + "evaluation_id": "eval_01HX4Y2P8BQ4Y3F0V0K9D6Z7M1", + "evaluated_at": "2026-04-30T20:15:00Z", + "reason": "approval_required", + "access_request": { + "endpoint": "https://pdp.example.com/access/v1/requests", + "template": "application_access", + "expires_at": "2026-04-30T20:25:00Z", + "binding_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBkcC0xIn0.eyJldmFsdWF0aW9uX2lkIjoiZXZhbF8wMUhYNFkyUDhCUTRZM0YwVjBLOUQ2WjdNMSJ9.bXBfc2lnbmF0dXJl", + "form_url": "https://requests.example.com/forms/application_access", + "request_schema_url": "https://requests.example.com/schemas/application_access.json", + "request_catalogs_url": "https://requests.example.com/catalogs/application_access.json", + "display": { + "title": "Request access", + "description": "Manager approval is required before this document can be opened." + } + } + } +} +~~~ + +# Catalogs Document {#catalogs-document} + +The Catalogs Document is a JSON object retrieved from `request_catalogs_url` using the HTTP `GET` method as defined in {{RFC9110}}. It has the following members: + +`fields`: +: REQUIRED. Object. Each member name is a JSON Pointer ({{RFC6901}}) into the form data instance described by the form schema, identifying a field whose value is selected from a catalog. Each member value is a Catalog Reference object. + +Implementations MAY include additional members for documentation or vendor metadata; consumers MUST ignore members they do not recognize. + +A Catalog Reference object has the following members: + +`endpoint`: +: REQUIRED. HTTPS URI. Catalog Endpoint from which catalog items are retrieved. + +`search_param`: +: OPTIONAL. String. Query parameter used to pass a free-text search term to the Catalog Endpoint. Defaults to `q`. + +`scope_params`: +: OPTIONAL. Object. Each member name is the query parameter sent to the Catalog Endpoint and the value is a JSON Pointer ({{RFC6901}}) into the form data instance identifying the source field. The PEP MUST resolve each pointer at request time and MUST NOT call the Catalog Endpoint until every referenced source field has a value. + +`value_path`: +: OPTIONAL. String. JSON Pointer ({{RFC6901}}) into a Catalog Item, identifying the value the PEP places into the form field. Defaults to `/value`. + +`label_path`: +: OPTIONAL. String. JSON Pointer ({{RFC6901}}) into a Catalog Item, identifying a human-readable label. Defaults to `/label`. + +Non-normative example: + +~~~ json +{ + "fields": { + "/application_id": { + "endpoint": "https://requests.example.com/catalog/applications", + "search_param": "q" + }, + "/entitlement_id": { + "endpoint": "https://requests.example.com/catalog/entitlements", + "search_param": "q", + "scope_params": { "application_id": "/application_id" } + } + } +} +~~~ + +# Catalog Endpoint {#catalog-endpoint} + +A Catalog Endpoint accepts an HTTP `GET` request as defined in {{RFC9110}} and returns a paginated list of Catalog Items. + +The Catalog Endpoint MUST accept the following query parameters: + +* The search parameter named by `search_param` (default `q`): String. Free-text query supplied by the caller. +* The scope parameters named by `scope_params`: String values taken from other form data fields. +* `cursor`: OPTIONAL. String. Opaque pagination cursor returned by a previous response. +* `limit`: OPTIONAL. Integer. Caller-requested page size. The Catalog Endpoint MAY clamp or ignore this value. + +The Catalog Endpoint MAY accept additional deployment-specific parameters; receivers MUST ignore parameters they do not recognize. + +Catalog Endpoints are protected APIs. Their authentication and credential rules are defined in {{authorization-and-authentication}}. + +A Catalog Endpoint MUST: + +* authenticate the caller; +* authorize the caller to enumerate the catalog; and +* return only items the caller is permitted to see for the original Subject, Resource, and Action. + +The catalog response is itself an authorization boundary; it MUST NOT disclose entries the requester would not be permitted to request. + +# Catalog Response {#catalog-response} + +A successful response returns HTTP `200 OK` and a JSON object with the following members: + +`items`: +: REQUIRED. Array of Catalog Items. Each Catalog Item is a JSON object containing the value identified by `value_path` and SHOULD include the value identified by `label_path`. Items SHOULD include the following well-known optional members when applicable, and MAY include additional vendor-specific metadata: + + * `description`: String. Human-readable description of the item. + * `risk_level`: String. Risk classification used by the deployment (for example, `low`, `medium`, `high`). Useful for agent and human triage. + * `granted`: Boolean. When `true`, indicates that the requester already has access to the item. Allows a PEP to suppress redundant or no-op Access Request submissions. + * `owner`: Object or String. Identifier or reference for the item's owner, when the catalog tracks ownership. + +`next_cursor`: +: OPTIONAL. String. Opaque cursor that the caller passes as `cursor` to retrieve the next page. Absent when no further pages are available. + +`total`: +: OPTIONAL. Integer. Approximate total number of items matching the search and scope filters. Used as a hint only; the PEP MUST NOT rely on its accuracy. + +Non-normative example: + +~~~ http +GET /catalog/entitlements?application_id=app_123&q=customer&limit=2 HTTP/1.1 +Host: requests.example.com +Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA +Accept: application/json +~~~ + +~~~ http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "items": [ + { + "value": "ent_abc", + "label": "Customer Records (Read)", + "description": "Read access to customer master data" + }, + { + "value": "ent_def", + "label": "Customer Records (Write)", + "description": "Write access to customer master data", + "risk_level": "high" + } + ], + "next_cursor": "eyJvZmZzZXQiOjJ9" +} +~~~ + +# Agent Protocol Catalogs {#catalog-agent-protocol} + +Deployments serving agentic PEPs MAY additionally expose catalogs through an agent protocol. When such a protocol is used, each catalog SHOULD be exposed as a resource whose identifier or URI template encodes the same scope parameters described by `scope_params` (for example, `entitlements://{application_id}`). Resource read responses SHOULD use the Catalog Response shape defined in {{catalog-response}}. + +This profile does not define agent-protocol discovery or transport. When both an HTTP Catalog Endpoint and an agent-protocol catalog are exposed, they MUST return the same Catalog Items for equivalent scope parameters. + +# Processing Rules + +## PEP Processing Rules {#pep-processing-rules} + +A PEP submitting an Access Request based on a form schema with a companion Catalogs Document: + +* When the requestable denial includes `request_catalogs_url`, MUST resolve catalog-backed fields according to this profile, or MUST NOT submit the Access Request if those fields cannot be resolved. +* MUST verify that `request_catalogs_url` and every catalog `endpoint` value resolve to hosts trusted under the deployment before fetching or acting on them, as required by {{trusting-catalog-urls}}. +* MUST treat field values resolved from a catalog as opaque identifiers; the value submitted is exactly the value identified by `value_path` in the chosen Catalog Item. +* MUST resolve every `scope_params` source field before calling the Catalog Endpoint for a dependent field. +* MUST NOT submit catalog values that were not returned by the Catalog Endpoint with the same scope parameters. +* SHOULD use `search_param` rather than enumerating large catalogs. +* MUST treat unknown members of a Catalog Item as informational and MUST NOT rely on them for enforcement. +* MUST NOT treat `granted` or any other Catalog Item member as an authorization decision. Such members MAY be used to suppress or shape Access Request submission, but MUST NOT be used as authorization input. + +## PDP Processing Rules + +A PDP implementing this profile: + +* MAY include `request_catalogs_url` in a requestable denial when the Access Request requires fields whose values are selected from a backing catalog. +* MUST include `request_schema_url` when including `request_catalogs_url`. +* MUST reference only Catalog Endpoints operated by, or trusted by, the Access Request Service for the deployment. + +## Access Request Service Processing Rules + +An Access Request Service implementing this profile: + +* MUST validate submitted catalog identifiers at submission time. +* MUST reject, normalize, or route for additional review any submitted catalog value that is no longer valid, no longer requestable by the caller, disabled, retired, or materially different in risk or ownership from the item resolved by the PEP. +* When operating Catalog Endpoints, MUST authenticate callers, MUST authorize callers to enumerate the catalog, and MUST return only Catalog Items the caller is permitted to see in the context of the original Subject, Resource, and Action. + +# Authorization and Authentication {#authorization-and-authentication} + +Catalog Endpoints are protected APIs. Support for OAuth 2.0 {{RFC6749}} is RECOMMENDED. When OAuth 2.0 bearer tokens are used, Catalog Endpoints MUST follow {{RFC6750}}. + +A Catalog Endpoint SHOULD share an origin with the Access Request Endpoint and SHOULD accept the same caller credentials. Deployments that host catalogs on a different origin MUST establish a documented mechanism for obtaining credentials accepted by the Catalog Endpoint, for example through OAuth 2.0 Token Exchange {{RFC8693}}; this profile does not define cross-origin credential acquisition. + +Authorization for a Catalog Endpoint call is bound to the original Subject, Resource, and Action of the denied evaluation rather than to a specific access token, session, or PEP instance. The Access Request Service MUST authorize each catalog call independently: authorization to submit an Access Request does not imply authorization to enumerate every catalog the deployment operates. + +# Extensibility {#extensibility} + +This profile populates the `context.access_request` extension point defined by {{ARAP}} with the `request_catalogs_url` member ({{requestable-denial-extension}}). + +This profile defines three additional extension points. Additional members MAY appear at these locations, and those members MUST follow the naming rules in the Naming Extensions section of {{ARAP}}: + +* A Catalogs Document ({{catalogs-document}}). +* A Catalog Reference object within a Catalogs Document ({{catalogs-document}}). +* A Catalog Item within a Catalog Response ({{catalog-response}}). + +An implementation receiving a member it does not recognize at an extension point defined by this profile MUST ignore it and MUST NOT fail processing on the basis of the unrecognized name. An implementation MAY surface unrecognized members in audit records or to a human requester, subject to the rule in {{pep-processing-rules}} that unknown Catalog Item members are informational only. + +Registry-eligible member names introduced by this profile are registered in {{member-names}}. + +# Privacy Considerations + +Search terms and scope parameters sent to a Catalog Endpoint reveal what the requester is looking for before any Access Request is submitted, and may themselves be personal data. A partially typed search term discloses requester intent even when no Access Request follows. + +Catalog Endpoints SHOULD minimize the logging and retention of query strings, and SHOULD retain them only as long as required by business, security, and compliance policy. + +Catalog Item metadata can identify people. The `owner` member in particular names or references an individual, and MUST only be returned to callers authorized to receive it. + +PEPs SHOULD NOT cache Catalog Responses beyond the request interaction that produced them. Catalog contents are scoped to the requester and to the Subject, Resource, and Action of the denied evaluation, so a cached response can outlive both the authorization that produced it and the accuracy of the items it holds. + +# Security Considerations + +## Trusting Catalog URLs {#trusting-catalog-urls} + +The `request_catalogs_url` member of a requestable denial, and the catalog `endpoint` values inside the Catalogs Document it references, are delivered to the PEP inside a denial response or inside a document fetched on the basis of that response. A compromised or misconfigured PDP, or an Access Request Service compelled by one, could direct the PEP at attacker-controlled hosts to substitute catalogs, harvest search terms and justifications, or perform credential phishing against the requester. + +An autonomous PEP MUST verify that these URLs resolve to hosts trusted under the deployment before fetching or acting on them, by requiring the same origin as the Access Request Endpoint advertised in PDP metadata or by maintaining an explicit allowlist of trusted Access Request Service hosts; a PEP that renders them for a human user SHOULD apply the same check. PEPs MUST NOT submit credentials to a host that is not trusted to receive them. + +## Catalog Disclosure + +Catalog Endpoints ({{catalog-endpoint}}) can leak sensitive information about applications, entitlements, organizational structure, or finance master data if not properly authorized. An attacker who can call a Catalog Endpoint without scoping or authorization can enumerate sensitive identifiers, infer access policy, or harvest catalog metadata. + +Mitigations: + +* Catalog Endpoints MUST authorize callers and MUST return only items the caller is permitted to see for the original Subject, Resource, and Action. +* Catalog Endpoints SHOULD apply rate limits and abuse detection commensurate with the sensitivity of the catalog they expose. +* PEPs SHOULD prefer searching with `search_param` over bulk enumeration. + +## Catalog Substitution and Stale Items + +A PEP resolves catalog values before submission, and an approval workflow may run for minutes, hours, or days afterward. An identifier that was valid at resolution time may be retired, re-pointed, or re-classified before the submission is accepted, and a compromised or defective PEP can submit an identifier it never resolved at all. + +A PEP MUST NOT submit catalog values that were not returned by the Catalog Endpoint with the same scope parameters, and MUST resolve every `scope_params` source field before calling the Catalog Endpoint for a dependent field. The PEP-side check is not sufficient on its own: the Access Request Service MUST validate submitted catalog identifiers at submission time and MUST reject, normalize, or route for additional review any value that is no longer valid, no longer requestable by the caller, disabled, retired, or materially different in risk or ownership from the item the PEP resolved. + +## Catalog Metadata Is Not Authorization {#catalog-metadata} + +Catalog Items carry metadata intended for presentation and triage. A PEP MUST treat unknown members of a Catalog Item as informational and MUST NOT rely on them for enforcement. In particular, a PEP MUST NOT treat `granted` or any other Catalog Item member as an authorization decision. Such members MAY be used to suppress or shape Access Request submission, for example to avoid submitting a request for access the requester already holds, but MUST NOT be used as authorization input. Authorization remains determined by the PDP at evaluation time and by the Access Request Service at submission time. + +# IANA Considerations + +This specification makes no requests of IANA. + +# OpenID Foundation Registry Considerations {#openid-foundation-registry-considerations} + +## AuthZEN Access Request Member Names Registry {#member-names} + +This specification registers the following entries in the AuthZEN Access Request Member Names registry established by {{ARAP}}. + +| Name | Extension Point | Description | +|---|---|---| +| `request_catalogs_url` | `context.access_request` | HTTPS URL of a Catalogs Document describing how catalog-backed form fields are resolved. | +| `description` | Catalog Item | Human-readable description of the catalog item. | +| `risk_level` | Catalog Item | Risk classification used by the deployment. | +| `granted` | Catalog Item | Boolean indicating the requester already has access to the item. | +| `owner` | Catalog Item | Identifier or reference for the item's owner. | + +Change Controller for all entries: OpenID Foundation AuthZEN Working Group. Specification Document for all entries: This document. + +--- back + +# Examples + +## End-to-End Catalog Resolution + +Alice attempts to read a document served by an application she holds no entitlement for. The PDP returns a requestable denial that points at both a form schema and a Catalogs Document, because the deployment's request form asks which application and which entitlement the request concerns and both values are selected from catalogs. + +### Requestable Denial + +~~~ http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "decision": false, + "context": { + "evaluation_id": "eval_01HX4Y2P8BQ4Y3F0V0K9D6Z7M1", + "evaluated_at": "2026-04-30T20:15:00Z", + "reason": "approval_required", + "access_request": { + "endpoint": "https://pdp.example.com/access/v1/requests", + "template": "application_access", + "expires_at": "2026-04-30T20:25:00Z", + "binding_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBkcC0xIn0.eyJldmFsdWF0aW9uX2lkIjoiZXZhbF8wMUhYNFkyUDhCUTRZM0YwVjBLOUQ2WjdNMSJ9.bXBfc2lnbmF0dXJl", + "form_url": "https://requests.example.com/forms/application_access", + "request_schema_url": "https://requests.example.com/schemas/application_access.json", + "request_catalogs_url": "https://requests.example.com/catalogs/application_access.json" + } + } +} +~~~ + +### Form Schema + +The following is a non-normative example of the document retrieved from `request_schema_url`: + +~~~ json +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://requests.example.com/schemas/application_access.json", + "type": "object", + "properties": { + "application_id": { "type": "string" }, + "entitlement_id": { "type": "string" }, + "business_justification": { "type": "string" }, + "requested_until": { "type": "string", "format": "date-time" } + }, + "required": [ + "application_id", + "entitlement_id", + "business_justification" + ] +} +~~~ + +The schema says nothing about catalogs. `application_id` and `entitlement_id` are plain strings; where their values come from is described separately by the Catalogs Document, and a PEP that understands only the schema still produces a structurally valid submission. + +### Catalogs Document {#example-catalogs-document} + +The following is a non-normative example of the document retrieved from `request_catalogs_url`: + +~~~ json +{ + "fields": { + "/application_id": { + "endpoint": "https://requests.example.com/catalog/applications", + "search_param": "q" + }, + "/entitlement_id": { + "endpoint": "https://requests.example.com/catalog/entitlements", + "search_param": "q", + "scope_params": { "application_id": "/application_id" } + } + } +} +~~~ + +### Resolving the Application + +~~~ http +GET /catalog/applications?q=crm&limit=2 HTTP/1.1 +Host: requests.example.com +Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA +Accept: application/json +~~~ + +~~~ http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "items": [ + { + "value": "app_123", + "label": "CRM", + "description": "Customer relationship management platform" + }, + { + "value": "app_456", + "label": "CRM Analytics", + "description": "Reporting and analytics over CRM data", + "granted": true + } + ], + "total": 2 +} +~~~ + +The PEP places `app_123` into the form data instance at `/application_id`. The `granted` member on the second item tells the PEP that Alice already has access to it; the PEP MAY use that to suppress a redundant submission, but MUST NOT treat it as an authorization decision. + +### Resolving the Entitlement + +The Catalog Reference for `/entitlement_id` declares a scope parameter sourced from `/application_id`, so the PEP can call the entitlement catalog only after the application has been resolved. + +~~~ http +GET /catalog/entitlements?application_id=app_123&q=customer&limit=2 HTTP/1.1 +Host: requests.example.com +Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA +Accept: application/json +~~~ + +~~~ http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "items": [ + { + "value": "ent_abc", + "label": "Customer Records (Read)", + "description": "Read access to customer master data" + }, + { + "value": "ent_def", + "label": "Customer Records (Write)", + "description": "Write access to customer master data", + "risk_level": "high" + } + ], + "next_cursor": "eyJvZmZzZXQiOjJ9" +} +~~~ + +### Submitting the Access Request + +~~~ http +POST /access/v1/requests HTTP/1.1 +Host: pdp.example.com +Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA +Content-Type: application/json +Idempotency-Key: 7b8d0f0d-65a1-4af1-9fd3-a684f08a5d13 + +{ + "subject": { + "type": "user", + "id": "alice@example.com" + }, + "resource": { + "type": "document", + "id": "q4-plan" + }, + "action": { + "name": "can_read" + }, + "context": { + "business_justification": "Needed for customer renewal review" + }, + "requested_access": { + "application_id": "app_123", + "entitlement_id": "ent_abc", + "requested_until": "2026-05-01T00:15:00Z" + }, + "denial": { + "evaluation_id": "eval_01HX4Y2P8BQ4Y3F0V0K9D6Z7M1", + "evaluated_at": "2026-04-30T20:15:00Z", + "expires_at": "2026-04-30T20:25:00Z", + "reason": "approval_required", + "binding_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBkcC0xIn0.eyJldmFsdWF0aW9uX2lkIjoiZXZhbF8wMUhYNFkyUDhCUTRZM0YwVjBLOUQ2WjdNMSJ9.bXBfc2lnbmF0dXJl", + "template": "application_access" + } +} +~~~ + +The Access Request Service re-validates `app_123` and `ent_abc` before accepting the submission. In this example the Catalog Endpoints are hosted on `requests.example.com` while the Access Request Endpoint is hosted on `pdp.example.com`; the deployment has arranged for the Catalog Endpoints to accept the same caller credentials, as recommended in {{authorization-and-authentication}}. From this point the Task Handle, task completion, and the re-evaluation after approval proceed exactly as defined in {{ARAP}}; this profile adds nothing to them. + +# Implementation Considerations {#impl-considerations} + +This appendix describes common deployment patterns and is non-normative. + +## Catalog Translation + +Most existing identity-governance, ITSM, and approval platforms have proprietary catalog APIs with vendor-specific request and response shapes. Implementations translate those APIs to the Catalogs Document and Catalog Endpoint protocol defined in this profile. Translation may be lossy for vendor-specific metadata; the Catalogs Document and Catalog Response need only carry enough information for an autonomous PEP to select and submit a value, while richer rendering details remain behind `form_url`. Deployments that expose tools or catalogs to autonomous agents through an agent protocol can additionally surface catalogs through that protocol; see {{catalog-agent-protocol}}. + +## Large and Dependent Catalogs + +Hierarchical selection is expressed by chaining `scope_params`: a Catalog Reference names the query parameters its endpoint needs and the JSON Pointers that supply them, so a PEP resolves parent fields first and dependent fields afterward. Deeper chains are possible, but each level adds a round trip and another point at which an autonomous PEP can stall, so deployments benefit from keeping chains shallow. + +Catalogs are frequently too large to enumerate. Implementations should prefer `search_param` over bulk retrieval, honor `next_cursor` for pagination rather than assuming offset semantics, and treat `total` as a display hint only; it may be approximate, expensive to compute, or omitted entirely. + +## Human and Agent PEPs + +The same Catalog Endpoints serve both PEP shapes. A human-facing request user interface renders a typeahead or picker directly from the endpoints an autonomous agent calls, using `label_path` for display and `value_path` for the submitted value, so the two surfaces cannot drift apart or disclose different sets of items. + +`granted` and `risk_level` support triage in both shapes: a user interface can dim entries the requester already holds and flag high-risk entries for extra confirmation, and an agent can decline to submit a redundant request or hand a high-risk selection to a human. Neither member is ever an authorization signal; see {{catalog-metadata}}. + +# Design Rationale {#design-rationale} + +This appendix records non-obvious design choices and the reasoning behind them. It is non-normative. + +## Why are catalog references kept outside the form schema? + +JSON Schema {{I-D.bhutton-json-schema}} describes the shape and constraints of a data instance. Expressing remote enumeration inside it would require either a custom vocabulary that generic validators ignore, or a static `enum` that cannot represent a catalog which is large, changes continuously, or is scoped per requester. Keeping the Catalogs Document as a sibling artifact lets the form schema remain a pure description of data shape, validated by any conformant validator, while resolution behavior lives in a document designed for it. + +## Why a companion profile rather than part of the Access Request and Approval Profile? + +Catalog resolution is optional in most deployments and absent entirely in the simplest ones, where the augmentations a submission carries are free text and timestamps. Folding a catalog protocol, its authorization rules, and its disclosure risks into the base profile would oblige every implementer to read them. Keeping catalogs in a companion profile keeps the base a thin wire format for the handoff between a denial and the workflow that resolves it, and lets this surface evolve without a revision of the base. + +## Why JSON Pointer for field addressing and item paths? + +The Catalogs Document has to name a field inside a form data instance whose shape is defined elsewhere, and a value inside a Catalog Item whose shape is partly vendor-defined. JSON Pointer {{RFC6901}} is a small, fully specified, unambiguous syntax for exactly that, already familiar from JSON Schema tooling, with no query language and no implementation-defined evaluation behavior to make interoperable. A richer expression language would add attack surface and divergence for no benefit at this scale. + +## Why must the Access Request Service re-validate catalog identifiers at submission time? + +The PEP's resolution is a convenience, not a security boundary. A PEP may be compromised, defective, or simply slow: an identifier resolved before an approval workflow ran may be retired or re-classified by the time the submission arrives, and a hostile PEP can submit any string it likes. The Access Request Service is the only party positioned to decide whether a value is still valid and still requestable by this caller, so the check belongs there regardless of what the PEP did. + +## Why is `granted` informational rather than an authorization signal? + +`granted` answers a question about the catalog, not about the current evaluation: it says the requester holds some access to the item, not that the denied Subject, Resource, and Action would now be allowed. Treating it as authorization would move an enforcement decision into a presentation surface that is optional, cacheable, and produced by a service that is not the PDP. Its value is in suppressing pointless requests, and that is all this profile lets it do. + +# Acknowledgements + +The author thanks the OpenID AuthZEN Working Group for discussion and review. + +# Document History + +-00 + +* Initial version. Catalog references were moved here from the AuthZEN Access Request and Approval Profile so that the base profile remains a thin wire format.