From 5387eab2af7411313b2380e3e5ea848d3008f085 Mon Sep 17 00:00:00 2001 From: ana Date: Tue, 14 Jul 2026 05:52:10 +0000 Subject: [PATCH 01/56] docs(spec): track AAuth Events draft in update workflow Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 40fd3f5f-2525-470a-bad7-831cdaa6b7dc --- .../instructions/update-spec.instructions.md | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/.github/instructions/update-spec.instructions.md b/.github/instructions/update-spec.instructions.md index 8b416cb..36a8f84 100644 --- a/.github/instructions/update-spec.instructions.md +++ b/.github/instructions/update-spec.instructions.md @@ -42,6 +42,7 @@ Each `v/` snapshot contains: | `draft-hardt-oauth-aauth-protocol.md` | AAuth repo | The protocol spec; names the snapshot. | | `draft-hardt-aauth-bootstrap.md` | AAuth repo | Informational; often unchanged across drafts. | | `draft-hardt-aauth-r3.md` | AAuth repo | Rich Resource Requests; has its own version. | +| `draft-hardt-aauth-events.md` | AAuth repo | AAuth Events; standalone draft. Include when present upstream. | | `interop-demo-profile.md` | AAuth repo | Informational; new since draft-06. Include when present upstream. | | `draft-hardt-httpbis-signature-key-.txt` | IETF archive | Lives in the separate `dickhardt/signature-key` repo; vendor the IETF `.txt` at the version the protocol references. | @@ -75,9 +76,19 @@ TAG=draft-hardt-oauth-aauth-protocol-$NN BASE="https://raw.githubusercontent.com/dickhardt/AAuth/$TAG" mkdir -p "v$NN" for f in draft-hardt-oauth-aauth-protocol.md draft-hardt-aauth-bootstrap.md \ - draft-hardt-aauth-r3.md interop-demo-profile.md; do + draft-hardt-aauth-r3.md; do curl -fsSL "$BASE/$f" -o "v$NN/$f" done + +# Optional standalone/informational docs; include when present upstream. +for f in draft-hardt-aauth-events.md interop-demo-profile.md; do + if curl -fsSL "$BASE/$f" -o "v$NN/$f"; then + echo "Downloaded optional $f" + else + rm -f "v$NN/$f" + echo "Optional $f not present upstream; record as absent." + fi +done ``` For the HTTP Signature Keys dependency, vendor the IETF `.txt` at the version the @@ -98,6 +109,9 @@ Record "unchanged" docs explicitly so each snapshot stays auditable. ```bash diff -q v02/draft-hardt-aauth-bootstrap.md v08/draft-hardt-aauth-bootstrap.md diff -q v02/draft-hardt-aauth-r3.md v08/draft-hardt-aauth-r3.md +if [ -f v02/draft-hardt-aauth-events.md ] && [ -f v08/draft-hardt-aauth-events.md ]; then + diff -q v02/draft-hardt-aauth-events.md v08/draft-hardt-aauth-events.md +fi # Protocol structural delta (added/removed sections): diff <(grep -nE '^#{1,2} ' v02/draft-hardt-oauth-aauth-protocol.md | sed 's/^[0-9]*://') \ <(grep -nE '^#{1,2} ' v08/draft-hardt-oauth-aauth-protocol.md | sed 's/^[0-9]*://') @@ -110,7 +124,8 @@ diff <(grep -nE '^#{1,2} ' v02/draft-hardt-oauth-aauth-protocol.md | sed 's/^[0- - Append a `## \`v/\` — protocol draft-NN` section (sections are in **ascending** order). Include a metadata table (source repo, commit SHA, commit date, tagged version, document date, IETF draft URL, copied-on date) and a per-file list - noting which docs are unchanged from the prior snapshot. + noting which docs are unchanged from the prior snapshot, including AAuth Events + when present. - Add a short "Notable changes since draft-XX" highlight list. ### 5. Update `CHANGELOG.md` @@ -120,7 +135,7 @@ diff <(grep -nE '^#{1,2} ' v02/draft-hardt-oauth-aauth-protocol.md | sed 's/^[0- per-snapshot sections (newest first). - Summarize the delta by theme, then reproduce the **author's verbatim per-draft changelog** from the protocol's `# Document History` section for every draft - spanned. Note unchanged R3/bootstrap, any new documents, and the + spanned. Note unchanged R3/bootstrap/AAuth Events, any new documents, and the signature-key version bump. - Reference the spec's own kramdown anchors (e.g. `#sub-agents`) rather than line numbers where possible — anchors are stable across line shifts. @@ -142,6 +157,7 @@ permanent home and survives the repo being moved, renamed, or deprecated: - Document page (all revisions) — - Per-revision text — `https://www.ietf.org/archive/id/draft-hardt-oauth-aauth-protocol-.txt` - Per-revision HTML — `https://datatracker.ietf.org/doc/html/draft-hardt-oauth-aauth-protocol-` +- AAuth Events editor's copy — If the GitHub repo is unavailable, vendor the Datatracker `.txt` rendering for that revision instead of the kramdown `.md`, and note the substitution in the snapshot's From 436f6acd29647a8ef7dec5f849dd648acc5ba06d Mon Sep 17 00:00:00 2001 From: ana Date: Tue, 14 Jul 2026 06:06:57 +0000 Subject: [PATCH 02/56] Vendor AAuth protocol draft 09 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 68b1ef25-c3f0-45c8-91da-3b652bf6572c --- aauth-spec/CHANGELOG.md | 167 +- aauth-spec/SPEC-VERSION.md | 63 +- aauth-spec/v09/draft-hardt-aauth-bootstrap.md | 411 ++ aauth-spec/v09/draft-hardt-aauth-events.md | 758 ++++ aauth-spec/v09/draft-hardt-aauth-r3.md | 665 ++++ .../draft-hardt-httpbis-signature-key-06.txt | 2184 +++++++++++ .../v09/draft-hardt-oauth-aauth-protocol.md | 3330 +++++++++++++++++ aauth-spec/v09/interop-demo-profile.md | 64 + 8 files changed, 7621 insertions(+), 21 deletions(-) create mode 100644 aauth-spec/v09/draft-hardt-aauth-bootstrap.md create mode 100644 aauth-spec/v09/draft-hardt-aauth-events.md create mode 100644 aauth-spec/v09/draft-hardt-aauth-r3.md create mode 100644 aauth-spec/v09/draft-hardt-httpbis-signature-key-06.txt create mode 100644 aauth-spec/v09/draft-hardt-oauth-aauth-protocol.md create mode 100644 aauth-spec/v09/interop-demo-profile.md diff --git a/aauth-spec/CHANGELOG.md b/aauth-spec/CHANGELOG.md index a751108..fe8938c 100644 --- a/aauth-spec/CHANGELOG.md +++ b/aauth-spec/CHANGELOG.md @@ -7,28 +7,39 @@ snapshots in this repository. Each snapshot is a self-contained folder; see [`SPEC-VERSION.md`](SPEC-VERSION.md) for source commits and metadata. Entries are grouped by snapshot folder, so every document in a release (protocol, -R3, bootstrap) is listed together and a change to R3 or bootstrap travels with the -protocol version it shipped in. +R3, bootstrap, AAuth Events, and informational profiles) is listed together and a +change to a companion document travels with the protocol version it shipped in. Section and line references in the **`v02/`** entry point into the draft-02 files under [`v02/`](v02/) (commit `feda56b`); references in the **`v08/`** entry point -into the draft-08 files under [`v08/`](v08/) (commit `dd2b852`). Anchors in -parentheses (e.g. `#sub-agents`) are the spec's own kramdown anchors and are stable -across line shifts. - -| Snapshot | Protocol | Bootstrap | R3 | Source commit | -|---|---|---|---|---| -| [`v01/`](v01/) | draft-01 | draft-01 | draft-00 | `c090879` (2026-05-11) | -| [`v02/`](v02/) | draft-02 | draft-01 (unchanged) | draft-00 (revised) | `feda56b` (2026-06-09) | -| [`v08/`](v08/) | draft-08 | draft-01 (unchanged) | draft-00 (unchanged) | `dd2b852` (2026-06-25) | - -> **The SDK code targets `v02/` (draft-02).** The `v08/` snapshot was vendored -> 2026-06-25 as the latest upstream reference; migrating the SDK to draft-08 is -> tracked separately and has not started. See +into the draft-08 files under [`v08/`](v08/) (commit `dd2b852`); references in the +**`v09/`** entry point into the draft-09 files under [`v09/`](v09/) (commit +`90089f8`). Anchors in parentheses (e.g. `#sub-agents`) are the spec's own +kramdown anchors and are stable across line shifts. + +| Snapshot | Protocol | Bootstrap | R3 | Events | Source commit | +|---|---|---|---|---|---| +| [`v01/`](v01/) | draft-01 | draft-01 | draft-00 | — | `c090879` (2026-05-11) | +| [`v02/`](v02/) | draft-02 | draft-01 (unchanged) | draft-00 (revised) | — | `feda56b` (2026-06-09) | +| [`v08/`](v08/) | draft-08 | draft-01 (unchanged) | draft-00 (unchanged) | — | `dd2b852` (2026-06-25) | +| [`v09/`](v09/) | draft-09 | draft-01 (unchanged) | draft-00 (revised) | draft-00 (new) | `90089f8` (2026-07-05) | + +> **The SDK code targets `v08/` (draft-08).** The `v09/` snapshot was vendored +> 2026-07-14 as the latest upstream reference; it does not change SDK behavior. See > [`SPEC-VERSION.md`](SPEC-VERSION.md). ## Contents +- [`v09/` — AAuth draft-09 snapshot](#v09--aauth-draft-09-snapshot) + - [Protocol (draft-09)](#protocol-draft-09) + - [1. Clarification response discriminator](#1-clarification-response-discriminator) + - [2. RFC 9457 problem details](#2-rfc-9457-problem-details) + - [3. AAuth Events integration](#3-aauth-events-integration) + - [AAuth Events (draft-00, new)](#aauth-events-draft-00-new) + - [R3 (draft-00, revised in place)](#r3-draft-00-revised-in-place) + - [Bootstrap and Interoperability Demo Profile (unchanged)](#bootstrap-and-interoperability-demo-profile-unchanged) + - [HTTP Signature Keys (draft-06)](#http-signature-keys-draft-06) + - [Author's verbatim changelog (draft-09)](#authors-verbatim-changelog-draft-09) - [`v08/` — AAuth draft-08 snapshot](#v08--aauth-draft-08-snapshot) - [Protocol (drafts 03–08)](#protocol-drafts-0308) - [1. Agent-delegation restructure](#1-agent-delegation-restructure) @@ -56,9 +67,133 @@ across line shifts. --- +## `v09/` — AAuth draft-09 snapshot + +The latest upstream snapshot, vendored 2026-07-14. It bundles protocol +**draft-09** with the newly added standalone AAuth Events **draft-00**, the +revised-in-place R3 **draft-00**, unchanged bootstrap **draft-01**, the unchanged +Interoperability Demo Profile, and HTTP Signature Keys **draft-06**. + +> **The SDK remains on draft-08.** The entries below measure draft-09 against the +> prior [`v08/`](v08/) snapshot for reference; no SDK migration is included. + +### Protocol (draft-09) + +Published as IETF +[draft-hardt-oauth-aauth-protocol-09](https://datatracker.ietf.org/doc/draft-hardt-oauth-aauth-protocol/09/) +(commit `90089f8`, document date 2026-06-17). The delta from draft-08 has three +themes; the author's verbatim draft-09 changelog is reproduced below. + +#### 1. Clarification response discriminator + +POST responses to a clarification at the pending URL are now self-describing: + +- Every POST body MUST contain an **`action`** member. +- `action: "clarification_response"` accompanies `clarification_response`; + `action: "updated_request"` accompanies a replacement `resource_token`. +- A server MUST reject a missing or unrecognized `action` with `400 Bad Request`. +- The clarification and updated-request examples now include the discriminator. + +#### 2. RFC 9457 problem details + +AAuth error bodies now use the common error response format at +`#error-response-format`: + +- `Content-Type` changed from `application/json` to + **`application/problem+json`** under RFC 9457. +- The AAuth **`error`** code is a REQUIRED problem-details extension member; + receivers use it to decide how to proceed. +- RFC 9457 **`detail`** replaces `error_description`; standard problem members + (`type`, `title`, `status`, `instance`) MAY also be present. +- Token-endpoint and polling examples were added, and affected endpoint text and + the mission-termination example now use the common format. + +#### 3. AAuth Events integration + +The protocol now links the newly standalone Events draft into the AAuth family: + +- Asynchronous event delivery is listed as a protocol capability, with the AP + acting as the agent's event router. +- AP metadata adds the optional **`event_endpoint`** field, required when the AP + supports AAuth Events. +- The IANA discussion references the Events-defined **`aa-subscribe+jwt`** and + **`aa-event+jwt`** type values. + +### AAuth Events (draft-00, new) + +[`draft-hardt-aauth-events.md`](v09/draft-hardt-aauth-events.md) is a new +standalone editor's draft (document date 2026-06-24). Its Document History labels +it `draft-hardt-aauth-events-00` and records “Initial draft.” No Datatracker +revision had been published as of the 2026-07-14 snapshot, so the file is pinned +to the protocol tag's source commit. + +The draft defines: + +- A four-phase **AP-as-inbox** flow: subscribe-token acquisition, subscription + registration, resource-to-AP event delivery, and platform-dependent AP-to-agent + delivery. +- AP-issued **subscribe tokens** (`typ: aa-subscribe+jwt`) carrying agent + identity, resource audience, confirmation key, event ID (`eid`), expiry, and + optional `max_uses`; agents present them as the `Signature-Key` JWT. +- Public subscriptions and protected subscriptions using a short-lived, + single-use, agent-bound **subscription ticket URL** from an earlier authorized + resource interaction. +- Resource-issued **event tokens** (`typ: aa-event+jwt`) carrying resource + identity, agent audience, `eid`, and response-window expiry. Resources send the + token and optional AsyncAPI-defined payload to the AP, which validates and + durably records delivery before returning `202 Accepted`. +- Event discovery through the R3 + **`urn:aauth:vocabulary:asyncapi`** vocabulary and an `aauth-subscribe` + AsyncAPI security scheme. +- Replay, token-scope, ticket, intermediary, enumeration, and payload-privacy + considerations, plus JWT type and R3 vocabulary registration requests. + +### R3 (draft-00, revised in place) + +R3 retains its draft-00 label and 2026-03-24 document date but differs from +`v08/`: + +- It adds a reference to AAuth Events. +- The AsyncAPI vocabulary now describes resources that **emit events**, makes + its `action` member optional, and notes that subscribing agents use `receive`. +- An R3 grant for an AsyncAPI subscription operation is connected explicitly to + the AAuth Events subscription-ticket and subscribe-token flow. + +### Bootstrap and Interoperability Demo Profile (unchanged) + +- **Bootstrap** remains draft-01 and is byte-identical to `v08/`. +- **Interoperability Demo Profile** is byte-identical to `v08/`. + +### HTTP Signature Keys (draft-06) + +Bumped **draft-05 → draft-06** +([`draft-hardt-httpbis-signature-key-06.txt`](v09/draft-hardt-httpbis-signature-key-06.txt)), +the revision cited by the published protocol draft-09. Draft-06 adds the +`self-jwt` standalone scheme: the self-issued JWT's signing key, discovered via +`{iss}/.well-known/{dwk}`, is reused as the HTTP signing key without a `cnf` +claim. + +### Author's verbatim changelog (draft-09) + +Reproduced from the Document History section of +[`v09/draft-hardt-oauth-aauth-protocol.md`](v09/draft-hardt-oauth-aauth-protocol.md): + +> **draft-hardt-oauth-aauth-protocol-09** +> +> - Clarification chat: added a required `action` discriminator +> (`clarification_response` / `updated_request`) to the agent's POST responses +> on the pending URL, so the response type is explicit rather than inferred +> from key presence. +> - Error responses: adopted RFC 9457 problem details — error bodies use +> `Content-Type: application/problem+json` with the AAuth error code as a +> required `error` extension member; `error_description` replaced by the RFC +> 9457 `detail` member; added token endpoint and polling error examples. + +--- + ## `v08/` — AAuth draft-08 snapshot -The latest upstream snapshot, vendored 2026-06-25 for reference. It bundles +The prior upstream snapshot, vendored 2026-06-25 for reference. It bundles protocol **draft-08** with the unchanged R3 (**draft-00**) and bootstrap (**draft-01**), adds the new informational **Interoperability Demo Profile**, and bumps the HTTP Signature Keys draft to **draft-05**. diff --git a/aauth-spec/SPEC-VERSION.md b/aauth-spec/SPEC-VERSION.md index 4046a27..a792008 100644 --- a/aauth-spec/SPEC-VERSION.md +++ b/aauth-spec/SPEC-VERSION.md @@ -3,9 +3,9 @@ These spec files were copied from the [AAuth](https://github.com/dickhardt/AAuth) repository for reference while building the .NET samples. They are grouped by the AAuth protocol draft version under [`v01/`](v01/), [`v02/`](v02/), and -[`v08/`](v08/). Each folder is a self-contained snapshot, so each carries its own -copy of the HTTP Signature Keys draft at the version that snapshot's protocol -references. +[`v08/`](v08/), and [`v09/`](v09/). Each folder is a self-contained snapshot, so +each carries its own copy of the HTTP Signature Keys draft at the version that +snapshot's protocol references. The GitHub repository is the working source we vendor from. The canonical, permanent home is the **IETF Datatracker**, which retains every published revision @@ -27,8 +27,9 @@ opaque-token flow (resource-managed, two-party access), added under sub-agent (S5) interop demo is deferred, though the parent-mediated code path is implemented and conformance-tested. -`v08/` is therefore both the version the SDK conforms to and the latest upstream -snapshot, vendored 2026-06-25. The earlier draft-02 ([`v02/`](v02/)) and draft-01 +`v08/` remains the version the SDK conforms to. The latest upstream reference is +draft-09 ([`v09/`](v09/)), vendored 2026-07-14; it includes the newly added AAuth +Events draft. The earlier draft-02 ([`v02/`](v02/)) and draft-01 ([`v01/`](v01/)) snapshots are retained for reference. For a high-fidelity record of what changed between snapshots, see @@ -138,3 +139,55 @@ draft-08 bundles six published protocol drafts (03 → 08). The headline deltas: - **New `## PS Approval Endpoint Authentication`** section and an implementation-clarity pass (draft-06): `AAuth-Requirement`/`AAuth-Access`/`AAuth-Capabilities` grammar, JWKS same-`kid` refresh, and structured `cnf.jwk` verification ordering. + +## `v09/` — protocol draft-09 + +> This is the latest upstream reference snapshot. The SDK still targets draft-08; +> vendoring draft-09 does not migrate SDK behavior. + +| Field | Value | +|---|---| +| Source repository | | +| Commit | `90089f80eaccccbd22e32e06946e2aa08f7d67fe` | +| Commit date | 2026-07-05 | +| Tagged version | `draft-hardt-oauth-aauth-protocol-09` | +| Protocol document date | 2026-06-17 | +| AAuth Events document date | 2026-06-24 | +| IETF draft | | +| Copied on | 2026-07-14 | + +- `draft-hardt-oauth-aauth-protocol.md` — Main AAuth protocol specification + (draft-09) +- `draft-hardt-aauth-bootstrap.md` — Agent bootstrap guidance (draft-01, + byte-identical to `v08/`) +- `draft-hardt-aauth-r3.md` — Rich Resource Requests (R3) specification (draft-00, + revised from `v08/` to connect its AsyncAPI vocabulary to AAuth Events) +- `draft-hardt-aauth-events.md` — AAuth Events specification (draft-00; **new** in + this snapshot). The tagged editor's copy is pinned by the snapshot commit; no + IETF Datatracker revision had been published as of the copied-on date. +- `interop-demo-profile.md` — Interoperability Demo Profile (informational, + byte-identical to `v08/`) +- `draft-hardt-httpbis-signature-key-06.txt` — HTTP Signature Keys + (Internet-Draft, draft-06; bumped from draft-05 in `v08/`). Downloaded + 2026-07-14 from + + (Internet-Draft, 2 July 2026 revision). The published protocol draft-09 cites + this revision. + +### Notable changes since draft-08 + +- **AAuth Events draft-00** adds AP-routed asynchronous events, subscribe and + event tokens, public and protected subscription registration, and AsyncAPI/R3 + discovery. +- **Protocol integration for Events** adds the AP `event_endpoint` metadata field, + describes the AP's event-router role, and references the `aa-subscribe+jwt` and + `aa-event+jwt` types. +- **Clarification responses** now require an explicit `action` discriminator with + `clarification_response` or `updated_request`. +- **Error responses** now use RFC 9457 problem details with + `Content-Type: application/problem+json`, the AAuth `error` extension member, + and `detail` instead of `error_description`. +- **R3 AsyncAPI vocabulary** now links granted event operations to the AAuth + Events subscription-ticket and subscribe-token flow. +- **HTTP Signature Keys draft-06** adds the `self-jwt` scheme for a self-issued + JWT whose discovered signing key also verifies the HTTP message signature. diff --git a/aauth-spec/v09/draft-hardt-aauth-bootstrap.md b/aauth-spec/v09/draft-hardt-aauth-bootstrap.md new file mode 100644 index 0000000..39b27f8 --- /dev/null +++ b/aauth-spec/v09/draft-hardt-aauth-bootstrap.md @@ -0,0 +1,411 @@ +%%% +title = "AAuth Bootstrap Guidance" +abbrev = "AAuth-Bootstrap" +ipr = "trust200902" +area = "Security" +workgroup = "TBD" +keyword = ["agent", "authorization", "bootstrap", "http", "identity"] +category = "info" + +[seriesInfo] +status = "informational" +name = "Internet-Draft" +value = "draft-hardt-aauth-bootstrap-latest" +stream = "IETF" + +date = 2026-05-06T00:00:00Z + +[[author]] +initials = "D." +surname = "Hardt" +fullname = "Dick Hardt" +organization = "Hellō" + [author.address] + email = "dick.hardt@gmail.com" + +%%% + + + + AAuth Protocol + + Hellō + + + + + + + + HTTP Signature Keys + + Hellō + + + + + + + + Web Authentication: An API for accessing Public Key Credentials - Level 3 + + W3C + + + + + + + + Establishing your app's integrity (App Attest) + + Apple + + + + + + + + Play Integrity API + + Google + + + + + + + + Web Cryptography API + + W3C + + + + + + +.# Abstract + +This document provides informational guidance for agent providers (APs) on enrolling agents and issuing AAuth agent tokens defined in [@!I-D.hardt-oauth-aauth-protocol]. It covers per-platform key handling, optional platform attestation, agent identifier strategies, and refresh patterns. The mechanisms described here are not normative protocol — they are common patterns that interoperable AP implementations can adopt or adapt. + +.# Discussion Venues + +*Note: This section is to be removed before publishing as an RFC.* + +Discussion of this document takes place on GitHub at https://github.com/dickhardt/AAuth. Issues, comments, and pull requests are welcome there. Source for this draft is in the same repository. + +{mainmatter} + +# Introduction + +The AAuth Protocol [@!I-D.hardt-oauth-aauth-protocol] establishes that every agent has its own cryptographic identity — an agent identifier of the form `aauth:local@domain`, bound to a signing key, and attested by an agent token issued by an agent provider (AP). The protocol defines the agent token format and how agents present that identity to person servers (PSes), resources, and access servers (ASes). It does not specify how an agent comes to hold an agent token in the first place. That step is **bootstrap**, and it is the subject of this document. + +## What Bootstrapping Is + +Bootstrapping is the AP-side ceremony by which an instance of an agent acquires an agent token. The agent generates a signing key on the device or in the browser where it will run, presents whatever evidence the AP requires (a signed-in account, an attested device, a published JWKS, etc.), and receives an agent token whose `cnf.jwk` is bound to that key and whose `sub` is an `aauth:local@domain` identifier the AP has chosen. + +After bootstrap the agent can participate in AAuth: it can sign HTTP messages per [@!I-D.hardt-httpbis-signature-key], identify itself at resources, and present its agent token to a PS so the user can bind the agent to themselves on first interaction. + +## What Bootstrapping Is Not + +- **Not normative protocol.** This document is informational. The AAuth Protocol does not mandate a specific bootstrap ceremony, and conformance does not depend on the patterns described here. APs are free to use other approaches that produce a valid agent token. +- **Not the user-to-agent binding.** Binding an agent to a person is performed by the PS, lazily, on the agent's first interaction with the PS per the AAuth Protocol. Bootstrap produces an agent identity; the PS attaches that identity to a user. +- **Not authorization.** Bootstrap conveys no scope, no resource permission, and no user identity claims. Those are obtained through the flows defined in the AAuth Protocol after bootstrap. +- **Not one-size-fits-all.** Web, mobile, and self-hosted agents have different threat models and different platform primitives available to them. This document offers patterns appropriate to each, not a single prescribed ceremony. + +## Patterns Covered + +- **Per-platform key handling** — where the agent's signing key lives and how strongly it is protected on web, mobile, and self-hosted deployments; desktop and workload coverage is TBD (#per-platform-keys). +- **Optional platform attestation** — when and why to require WebAuthn, App Attest, or Play Integrity (#optional-attestation). +- **Agent identifier strategies** — how to construct the `sub` claim's local part (#identifier-strategies). +- **Refresh patterns** — issuing fresh agent tokens for renewal (#refresh-patterns). + +Throughout, when this document refers to "the durable key" and "the ephemeral key" it means the keys defined in (#per-platform-keys). The ephemeral key's public part appears in `agent_token.cnf.jwk` and signs HTTP messages from the agent per [@!I-D.hardt-httpbis-signature-key]; the durable key serves as the AP's stable enrollment anchor and signs only at refresh. APs that use a single durable key for all signatures (#per-platform-keys) can read references to "the ephemeral key" as referring to that same durable key. + +# Conventions and Definitions + +{::boilerplate bcp14-tagged} + +This document is informational guidance and does not itself impose normative requirements. Normative requirements relevant to bootstrap are defined in [@!I-D.hardt-oauth-aauth-protocol]; this document references them where helpful but uses lowercase "should" / "must" in its own descriptive prose. + +# Terminology + +Terms defined in [@!I-D.hardt-oauth-aauth-protocol] are used here with the same meaning. In particular: + +- **Agent Provider (AP)** — issues agent tokens. +- **Agent token** — JWT signed by the AP, carrying `iss`, `sub`, `cnf.jwk`, optionally `ps`, and other claims. +- **Person Server (PS)** — represents the person; binds agents to a person on first interaction. + +This document additionally uses: + +- **Durable key** — a signing key whose lifetime is intended to span the agent install (typically the lifetime of an install or browser-storage entry). The durable key is the AP's stable enrollment anchor; it is presented only to the AP at refresh and is not used to sign requests to PSes, resources, or ASes. +- **Ephemeral key** — a signing key generated fresh per agent-token issuance. Its public part appears in `agent_token.cnf.jwk`. The agent uses it to sign HTTP messages for the agent token's lifetime, then discards it on the next refresh. +- **Platform attestation** — a mechanism by which the runtime platform attests to properties of the agent or its key (WebAuthn, Apple App Attest, Google Play Integrity, etc.). + +# Per-Platform Key Handling {#per-platform-keys} + +On web, mobile, and desktop, APs should use a two-key pattern: a **durable key** that serves as the AP's stable enrollment anchor and is presented only to the AP at refresh, plus an **ephemeral key** generated fresh per agent-token issuance whose public part appears in `agent_token.cnf.jwk` and which signs HTTP messages for the agent token's lifetime. Refresh chains the new ephemeral key to the durable key via the `jkt-jwt` scheme [@!I-D.hardt-httpbis-signature-key]; see (#refresh-patterns). + +This pattern bounds the blast radius of an ephemeral-key leak to one agent token's lifetime, narrows the durable key's attack surface to the AP refresh path (it never signs requests to PSes, resources, or ASes), and accommodates hardware-backed durable keys on platforms that have them today and on platforms that may expose them in the future without protocol change. APs may use a single durable key for all signatures where simplicity outweighs these properties — receivers cannot distinguish the two patterns, since they only verify `cnf.jwk` against the HTTP signature. + +Self-hosted agents (#self-hosted-agents) use a single key — the JWKS-published key serves as both the AP signing key and the agent's signing key, since there is no separate AP to refresh against. + +## Web Apps + +The durable key is a non-extractable [@WebCryptoAPI] key generated with `extractable: false` and stored in IndexedDB scoped to the AP's origin. The ephemeral key is also a non-extractable WebCrypto key, regenerated on each refresh and discarded when the next refresh produces its replacement. + +Properties of the durable key: + +- The private key cannot be read or exported by JavaScript, including by code injected via XSS or malicious browser extensions. JS can only ask the browser to sign with it. +- The key is bound to the origin's IndexedDB storage. Clearing site data destroys it; a new enrollment is required. +- No user-verification gesture is required to sign — operations are fast enough for routine signature use. + +Both keys are software-protected (browser sandbox) rather than hardware-protected. The two-key pattern still applies: the durable key signs only the periodic refresh (once per agent-token lifetime), while the ephemeral key signs every HTTP request and rotates on each refresh. APs that want the additional assurance of a WebAuthn ceremony at enrollment time can layer one on top; see (#optional-attestation). If browsers later expose hardware-backed credentials suitable for use as the durable key, the pattern accommodates them with no protocol change. + +## Mobile (iOS and Android) + +The durable key is generated and stored in the platform's hardware-backed keystore: the Secure Enclave on iOS or StrongBox on Android (or the Android Keystore where StrongBox is unavailable). The ephemeral key is a software key in app memory, regenerated on each refresh. + +Properties of the durable key: + +- The private key cannot be exported from the keystore. Cryptographic operations are performed by the keystore on the application's behalf. +- The key is bound to the application install. Reinstalling the app generates a new key and requires re-enrollment. +- The keystore can additionally enforce user authentication (biometric or device passcode) before sign operations, if the AP wants user-presence on each refresh. + +The AP typically also requires an attestation ceremony at enrollment to confirm the durable key is real keystore-bound material rather than software-generated. See (#optional-attestation). Because the durable key signs only at refresh, any per-op cost (keystore round-trip, optional user verification) is incurred at most once per agent-token lifetime, not on every request. + +## Self-Hosted Agents {#self-hosted-agents} + +A self-hosted agent runs under a domain the user controls. The agent publishes its AP metadata document at `/.well-known/aauth-agent.json` per [@!I-D.hardt-oauth-aauth-protocol]; the JWKS itself is hosted at any HTTPS URL referenced by the metadata's `jwks_uri`. The corresponding private key should be hardware-bound where the platform supports it: macOS Keychain (Secure Enclave on supported hardware), Windows TPM, or Linux Secret Service. + +Self-hosted agents act as their own AP — they self-issue agent tokens signed by the JWKS-published key. There is no separate AP to refresh against, so the two-key pattern does not apply: the JWKS-published key serves both as the AP signing key (signing self-issued agent tokens) and as the key whose public part appears in `agent_token.cnf.jwk` (signing HTTP messages). Because the trust anchor is a key the user controls and publishes, no platform attestation step exists. Other parties verify the agent token signature against the published JWKS, exactly as they would for any other AP. + +## Desktop Apps + +TBD. Future revisions will cover key handling for native desktop applications, where the durable key would live in a hardware-backed store (macOS Keychain with Secure Enclave on supported hardware, Windows TPM via CNG, Linux Secret Service / TPM2) and the ephemeral key in process memory, following the same pattern as mobile. + +## Workload + +TBD. Future revisions will cover headless workload identity (e.g., SPIFFE/SPIRE SVIDs, WIMSE workload identity, cloud-platform IMDS attestation), where the trust anchor is platform attestation rather than user interaction. + +# Optional Platform Attestation {#optional-attestation} + +Platform attestation gives the AP cryptographic evidence about the runtime context in which the durable key was generated. It is optional — the AAuth Protocol does not require it. APs choose whether to require attestation based on their threat model. + +Common reasons an AP might require attestation: + +- **Anti-fraud at enrollment.** Distinguishing real user devices from server-side automation. +- **Hardware-binding evidence.** Confirming the durable key is in a Secure Enclave or StrongBox rather than software. +- **App-integrity evidence.** Confirming the agent is the AP's published app, not a modified or repackaged binary. +- **User-presence evidence.** Confirming a real user gesture authorized this enrollment. + +Common reasons an AP might not require attestation: + +- The AP serves a trust posture where AP-side fraud detection is not signal-driven (e.g., paid accounts, invitation-only enrollment). +- The AP wants the broadest possible reach, including environments where attestation is unavailable. +- The deployment is self-hosted, where the user is the trust anchor. + +## WebAuthn (Web Apps) + +[@WebAuthn] provides user-verification and a hardware-rooted credential on web. APs that require it perform a registration ceremony at enrollment and an assertion ceremony at later sensitive operations. The credential is stored in the user's authenticator (TPM, Secure Enclave, security key, or platform syncing fabric). + +Tradeoffs: + +- Provides user-verification (touch, face, PIN) the WebCrypto-only approach lacks. +- Provides hardware-protected key material that cannot be lifted even by a fully compromised browser process. +- UX failure modes exist around cross-Chrome-profile use, embedded webviews, syncing-fabric inconsistencies, and cross-device QR ceremonies. APs that adopt WebAuthn should plan for fallback paths when these fail. + +## App Attest (iOS) + +[@AppAttest] attests to two things: that the durable key was generated in the device's Secure Enclave, and that the request originates from the AP's published app on a genuine Apple device. The AP receives an attestation object at enrollment which it verifies against Apple's attestation root, then accepts subsequent assertions signed by the same enclave key. + +## Play Integrity (Android) + +[@PlayIntegrity] provides a device, app, and account integrity verdict signed by Google. The AP nominates a nonce, the agent invokes the Play Integrity API, and the AP verifies the resulting integrity token. Subsequent assertions can be signed by an Android Keystore (or StrongBox) key that the AP enrolled at the same time. + +## When to Require Attestation + +A practical rule of thumb: + +- **Consumer-grade APs**: optional. Most consumer agent providers do not require attestation; the protocol-level binding at the PS is the security gate. +- **Regulated or high-assurance APs**: usually required. Financial services, healthcare, enterprise SSO providers benefit from attestation as part of their AML/KYC or device-management posture. +- **Multi-tenant APs**: often required at higher tiers. An AP may issue weak agent tokens to free tier users and require attestation for paid or enterprise tier users, surfacing the difference to receivers via AP-defined claims in the agent token. + +# Agent Identifier Strategies {#identifier-strategies} + +The agent token's `sub` is an `aauth:local@domain` identifier. The `domain` part is the AP. The `local` part identifies the agent install at the AP — it must be stable for the lifetime of the install so PSes and other parties can recognize a returning agent. + +APs are free to choose any opaque scheme for the local part: a random string assigned at enrollment, a deterministic derivation from the durable key's thumbprint, a sequential identifier, or a human-readable handle. When deriving from a thumbprint, use the durable key's thumbprint — the ephemeral key rotates on each refresh and is not a stable identifier. Receivers treat the identifier as opaque. + +## Per-Install Identity {#per-install-identity} + +Each install's durable key is the basis for one agent identity. A returning user on a new device is a new agent. This keeps the AP minimal — it has no user-account system, no `(user, durable_jkt)` mappings, and no ability to correlate a single user's activity across their devices. + +Multi-device users will see multiple agent entries in their PS dashboard. Grouping or merging those entries belongs at the PS, which already authenticates the user and is the correct layer for cross-device correlation. Rotation of the durable key produces a new agent identity; rotation of the ephemeral key (on every refresh) does not — the agent's `sub` is stable across ephemeral rotations. PS-side regrouping is the recovery path for durable-key changes. + +# Example Agent Token Claims + +A typical agent token issued by an AP, illustrating the claims described in [@!I-D.hardt-oauth-aauth-protocol] and the identifier strategies in (#identifier-strategies). + +JWT header: + +```json +{ "alg": "EdDSA", "typ": "aa-agent+jwt", "kid": "..." } +``` + +JWT payload: + +```json +{ + "iss": "https://ap.example", + "dwk": "aauth-agent.json", + "sub": "aauth:k7q3p9n2@ap.example", + "ps": "https://ps.example", + "cnf": { "jwk": { "kty": "OKP", "crv": "Ed25519", "x": "..." } }, + "iat": 1746316800, + "exp": 1746320400, + "jti": "..." +} +``` + +# Refresh Patterns {#refresh-patterns} + +Agent token lifetime is the AP's policy re-evaluation cadence — every refresh is the AP's chance to re-check device posture, attestation freshness, and account status before issuing a new token. A typical lifetime is **1 hour**, matching common practice for proof-of-possession-bound access tokens. APs may use shorter lifetimes (e.g., 5–15 minutes) for higher-assurance deployments where attestation must be refreshed often, or longer lifetimes up to the AAuth Protocol's 24-hour ceiling for low-policy-churn deployments where refresh chattiness is undesirable. + +## Two-Key Refresh + +On web, mobile, and desktop, refresh chains the new ephemeral key to the durable key via the `jkt-jwt` scheme [@!I-D.hardt-httpbis-signature-key]: + +1. The agent generates a fresh ephemeral key pair. +2. The agent constructs a JWT signed by the **durable key**, naming the new ephemeral public key. This is the "naming JWT" carried in the `Signature-Key` header under `scheme=jkt-jwt`. +3. The agent signs the refresh request with the **ephemeral key** under [@!RFC9421] HTTP Message Signatures. +4. The AP verifies the durable-key signature on the naming JWT, looks up the enrollment by the durable key's thumbprint, verifies the HTTP signature against the ephemeral public key, applies its policy (device posture, attestation freshness, account status), and returns a new agent token whose `cnf.jwk` is the ephemeral public key. +5. The agent uses the new agent token and ephemeral key for the agent token's lifetime, then discards the ephemeral key on the next refresh. + +Example refresh request: + +```http +POST /refresh HTTP/1.1 +Host: ap.example +Content-Type: application/json +Signature-Input: sig=("@method" "@authority" + "@path" "signature-key");created=1746316800 +Signature: sig=:...ephemeral-key signature bytes...: +Signature-Key: sig=jkt-jwt;jwt="eyJhbGc..." + +{} +``` + +The `jwt` parameter value is a JWT signed by the durable key with payload including the ephemeral public key (typically as `cnf.jwk`) and a `jti` for replay protection. The HTTP signature is produced by the ephemeral key. The AP correlates the two keys via the naming JWT's payload. + +## Single-Key Refresh + +APs that opt for the single-durable pattern (#per-platform-keys) sign the refresh request directly with the durable key under the `hwk` scheme [@!I-D.hardt-httpbis-signature-key]. The AP verifies the signature, looks up the enrollment by the key's thumbprint, and issues a fresh agent token with a new `exp`. The same `cnf.jwk` is carried through; the agent's key is unchanged. + +## Mobile Refresh Specifics + +APs that required platform attestation at enrollment typically do not re-attest on every refresh — the durable-key signature on the naming JWT (or the durable-key HTTP signature in the single-key pattern) is sufficient proof that the same enclave-resident key is making the request. APs that want periodic re-attestation can require a fresh App Attest assertion or Play Integrity verdict on a schedule (e.g., every 30 days) by including a server nonce in the refresh challenge. + +## Self-Hosted Refresh + +Self-hosted agents self-issue agent tokens. There is no separate refresh ceremony — the agent generates a new agent token signed by its JWKS-published key whenever needed. The two-key pattern does not apply (#self-hosted-agents). + +## Key Rotation vs Token Refresh + +Refresh issues a new agent token bound to a fresh ephemeral key (or, in the single-key pattern, to the same durable key). **Durable key rotation** generates a new durable key and is a separate, rare event. Under the per-install identity model (#per-install-identity), a new durable key is a new agent — the PS treats it as new on first interaction, and any cross-device or cross-rotation continuity is handled at the PS by the user. + +# Per-Platform Enrollment Sketches + +This section sketches a typical end-to-end enrollment for each platform. The sketches are illustrative; APs are free to vary them. + +## Web App Enrollment + +1. User logs into the AP through the AP's normal login. +2. Agent (running in the AP's web origin) generates a non-extractable WebCrypto Ed25519 **durable** key and stores its handle in IndexedDB. +3. Agent posts the durable public key to an AP-internal enrollment endpoint, signed by the new key (`hwk` scheme). +4. AP optionally performs a WebAuthn registration and verifies it. +5. AP records `(ap_user, durable_jkt)` and is now ready to issue agent tokens. +6. When the agent needs an agent token directed at PS_X, it generates a fresh **ephemeral** WebCrypto key and calls an AP-internal token-issuance endpoint indicating `ps=PS_X`, signed via `jkt-jwt` chaining the durable key to the ephemeral key (#refresh-patterns). The AP returns an agent token with `sub` derived per the AP's identifier strategy (#identifier-strategies) (using the durable key's thumbprint when derivation is used), `ps = PS_X`, `cnf.jwk` = the ephemeral public key, and any AP-attested claims. + +## Mobile App Enrollment + +1. User signs into the AP through the app's normal login. +2. App generates a **durable** key in the Secure Enclave (iOS) or StrongBox (Android). +3. App initiates platform attestation: App Attest on iOS, Play Integrity on Android. The AP nominates a nonce. +4. App posts the durable public key, the attestation result, and the nonce to an AP-internal enrollment endpoint. +5. AP verifies the attestation against the platform's trust root. +6. AP records `(ap_user, durable_jkt, attestation)` and is ready to issue agent tokens. +7. Token issuance proceeds as in the web app sketch — app generates a fresh ephemeral key per agent token and chains it to the durable key via `jkt-jwt`. + +## Self-Hosted Enrollment + +1. User generates a hardware-bound key on their machine. +2. User publishes an AP metadata document at `/.well-known/aauth-agent.json` per [@!I-D.hardt-oauth-aauth-protocol], with `jwks_uri` pointing to a JWKS containing the public part of that key. +3. The agent self-issues an agent token signed by that key as needed. + +There is no separate enrollment step — publication of the JWKS is the enrollment. + +# Security Considerations + +## Trust in the AP + +Every AP-attested claim in the agent token is only as trustworthy as the AP that signed the token. Receivers should apply policy proportional to their trust in the AP. An unfamiliar AP making strong attestation claims may warrant additional caution at the PS consent screen. + +## Ephemeral Key Compromise + +An ephemeral-key leak — via memory disclosure, in-page attacker, side channel, or similar — exposes only the signatures the agent makes during the current agent token's lifetime. At the recommended 1-hour lifetime, the blast radius is bounded to roughly that window before natural expiry forces replacement. Agents that detect compromise can decline to refresh, aging out the ephemeral key without explicit revocation. This bounding is the primary security argument for the two-key pattern (#per-platform-keys). + +## Durable Key Compromise + +Compromise of the durable key compromises the install's agent identity for the durable key's lifetime. The durable key signs only at refresh and is presented only to the AP, so its attack surface is much narrower than the ephemeral key's — but a successful compromise lets the attacker mint refresh requests indefinitely until the AP revokes the enrollment. APs should detect anomalous refresh patterns and provide a way for users to revoke a durable enrollment. + +A non-extractable WebCrypto durable key cannot be exfiltrated by page-level attackers, but it can be used by them while they hold execution in the page. APs should pair WebCrypto-only enrollment with normal web hygiene (CSP, subresource integrity, dependency review) and should not treat the non-extractable property as a substitute for keeping the page's JavaScript clean. A hardware-backed durable key (Secure Enclave, StrongBox, TPM) cannot be exfiltrated at all, only used in-place — narrowing the threat to malicious code running in the agent application itself. + +## Attestation Replay + +Platform attestation results (App Attest, Play Integrity, WebAuthn ceremonies) should be bound to a server-nominated nonce that is single-use and short-lived (5 minutes is a reasonable upper bound). The AP should verify that the attestation includes the nonce it issued; without this binding, a captured attestation can be replayed across enrollments. + +## Self-Hosted JWKS Key Compromise + +Compromise of the self-hosted JWKS key allows the attacker to mint agent tokens for that user's domain. Users running self-hosted agents should use hardware-backed keys (Secure Enclave / TPM / StrongBox) and rotate the published JWKS if compromise is suspected. + +# Privacy Considerations + +## Identifier Stability and User Tracking + +An agent's `sub` is the same value at every PS the agent contacts, not a per-PS pairwise identifier. A stable `sub` lets each PS reliably re-identify the agent across sessions — that is the intended property — but it also means colluding PSes (or any party with cross-PS telemetry) can correlate the agent's activity across them. Under per-install identity (#per-install-identity), durable-key rotation produces a new `sub`, giving users a natural "fresh start" capability. + +## AP Visibility Into Agent Activity + +The AP that issued an agent token does not see the agent's subsequent traffic to PSes, resources, or ASes (they verify against the AP's published JWKS, not by calling the AP). The AP's view is limited to enrollment and refresh requests. APs should document their data retention practices for those events. + +# IANA Considerations + +This document is informational and registers no new media types, JWT claim names, or metadata fields. + +# Implementation Status + +*Note: This section is to be removed before publishing as an RFC.* + +This section records the status of known implementations of the patterns described in this document at the time of posting of this Internet-Draft, and is based on a proposal described in [@?RFC7942]. The description of implementations in this section is intended to assist the IETF in its decision processes in progressing drafts to RFCs. + +TBD + +# Document History + +*Note: This section is to be removed before publishing as an RFC.* + +- draft-hardt-aauth-bootstrap-01 + - Major rewrite. The document is now informational guidance for AP implementers. The previously-normative PS bootstrap protocol (PS `/bootstrap` endpoint, `bootstrap_token`, bootstrap announcement, agent server [now Agent Provider] `bootstrap_endpoint` / `refresh_endpoint` / `webauthn_endpoint`) has been removed. PS-side binding to a person now happens lazily on the agent's first interaction with the PS per the AAuth Protocol; the bootstrap document covers AP-side enrollment patterns only. + - Removed Agent-Attested Display Values section; the `platform` and `device` parameters are defined and described in the AAuth Protocol. + +- draft-hardt-aauth-bootstrap-00 + - Initial draft. + +# Acknowledgments + +TBD. + +{backmatter} diff --git a/aauth-spec/v09/draft-hardt-aauth-events.md b/aauth-spec/v09/draft-hardt-aauth-events.md new file mode 100644 index 0000000..15abfd9 --- /dev/null +++ b/aauth-spec/v09/draft-hardt-aauth-events.md @@ -0,0 +1,758 @@ +%%% +title = "AAuth Events" +abbrev = "AAuth-Events" +ipr = "trust200902" +area = "Security" +workgroup = "TBD" +keyword = ["agent", "events", "webhooks", "async", "subscribe", "http", "identity"] +category = "standard" + +[seriesInfo] +status = "standard" +name = "Internet-Draft" +value = "draft-hardt-aauth-events-latest" +stream = "IETF" + +date = 2026-06-24T00:00:00Z + +[[author]] +initials = "D." +surname = "Hardt" +fullname = "Dick Hardt" +organization = "Hellō" + [author.address] + email = "dick.hardt@gmail.com" + +%%% + + + + AAuth Protocol + + Hellō + + + + + + + + HTTP Signature Keys + + Hellō + + + Cloudflare + + + + + + + + AAuth Rich Resource Requests (R3) + + Hellō + + + + + + + + AAuth Bootstrap Guidance + + Hellō + + + + + + + + AsyncAPI Specification 3.0.0 + + AsyncAPI Initiative + + + + + + +.# Abstract + +This document defines AAuth Events — an event subscription and delivery mechanism for agents operating under the AAuth Protocol ([@!I-D.hardt-oauth-aauth-protocol]). It specifies the subscribe token that agents use to register callbacks with resources, the event token that resources deliver when events fire, and the delivery path through the Agent Provider (AP). AAuth Events enables agents to receive asynchronous notifications without requiring a public endpoint, using the cryptographic identity established by the AAuth Protocol. + +.# Discussion Venues + +*Note: This section is to be removed before publishing as an RFC.* + +Discussion of this document takes place on GitHub at https://github.com/dickhardt/AAuth. Issues, comments, and pull requests are welcome there. Source for this draft is in the same repository. + +{mainmatter} + +# Introduction + +## Agents Cannot Receive Webhooks + +Agents are often not servers with a routable endpoint. Whether running as a workload, a mobile app, or a single-page application, agents typically cannot receive inbound HTTP connections. Existing event delivery mechanisms — webhooks, WebSub, callback URLs — all assume the receiver is always-on and reachable. This assumption fails for agents that run intermittently, execute behind NAT, or live inside a platform that does not expose inbound HTTP. + +At the same time, many interactions agents initiate are inherently asynchronous. An agent books a medical appointment and needs to know if an earlier slot opens. An agent monitors inventory and needs to know when a product becomes available. An agent submits an order and needs confirmation when it ships. In each case, the agent initiates a synchronous request, the resource accepts it, and then the resource needs to reach back to the agent when something changes — potentially hours or days later. + +Existing approaches each fall short: + +- **Webhooks** require the agent to have a public URL. Agents do not. +- **Polling** is wasteful and imprecise. For time-sensitive events like waitlist slots, polling is too slow and too expensive. +- **Server-Sent Events / WebSocket** require a persistent outbound connection, which conflicts with intermittent agent workloads. +- **Message queues** (SQS, Kafka, RabbitMQ) require shared infrastructure, are not web-standard, and have no standardized subscription protocol across trust domains. + +## The Agent Provider as Inbox + +The AAuth Protocol establishes that every agent has an Agent Provider (AP) — a stable, always-on server that issues the agent's identity token. The AP is already a first-class principal in the AAuth ecosystem: it has its own cryptographic identity, publishes metadata at a well-known URL, and is trusted by all parties that interact with the agent. + +AAuth Events uses the AP as the agent's permanent event inbox. A resource that needs to notify an agent does not need to reach the agent directly — it posts the event to the AP's event endpoint. The AP delivers the event to the agent through whatever mechanism the AP and agent have established. The agent does not need a public URL. The AP is the public URL. + +## What AAuth Events Provides + +- **No public endpoint required**: The AP receives events on the agent's behalf. AP-to-agent delivery is platform-dependent and out of scope for this specification. +- **Cryptographic authorization**: The subscribe token is AP-signed and restricts event delivery to a specific resource. No shared secrets. +- **Agent identity at subscription time**: The resource knows cryptographically which agent subscribed, via the `sub` claim in the subscribe token. +- **Protected and public subscriptions**: Public event channels require only a subscribe token. Protected channels use a pre-authorized subscription URL issued by the resource during a prior authenticated interaction. +- **Event discovery via AsyncAPI**: Resources describe their event channels using AsyncAPI ([@AsyncAPI]) as an AAuth R3 vocabulary ([@!I-D.hardt-aauth-r3]). + +## Relationship to Existing Standards + +AAuth Events builds on the AAuth Protocol ([@!I-D.hardt-oauth-aauth-protocol]) and HTTP Signature Keys ([@!I-D.hardt-httpbis-signature-key]). It provides the transport and subscription mechanisms that AsyncAPI ([@AsyncAPI]) describes: resources use AsyncAPI to document their event channels and payload schemas, while AAuth Events defines how agents subscribe and how events are delivered. + +# Conventions and Definitions + +{::boilerplate bcp14-tagged} + +# Terminology + +Terms defined in [@!I-D.hardt-oauth-aauth-protocol] are used here with the same meaning. In particular: Agent, Agent Provider (AP), Agent Token, Resource, Resource Token, Auth Token, Person Server (PS), Access Server (AS), and HTTP Sig. + +This document additionally uses: + +- **Subscribe Token**: A JWT issued by the AP to the agent, authorizing a specific resource to deliver events to the AP on the agent's behalf. Contains the Event ID and the agent's current signing key. +- **Event ID (eid)**: An opaque, AP-generated identifier that uniquely identifies a subscription at the AP. The agent maps the `eid` to its own context. The `eid` is the correlation key between the subscribe token, the AP's subscription record, and the event token. +- **Event Token**: A JWT issued and signed by the resource when an event fires, addressed to the agent (`aud` = agent identifier), and delivered to the AP's event endpoint. +- **Event Endpoint**: An endpoint published by the AP in its metadata at which resources deliver event tokens. +- **Subscription Ticket**: An opaque, short-lived value returned by a resource in response to an authenticated interaction, pre-authorizing a subsequent subscription registration call. Used when subscription to a protected channel requires prior authenticated context. + +# Protocol Overview + +AAuth Events involves four phases: setup, subscription registration, event delivery from resource to AP, and event delivery from AP to agent. + +~~~~ ascii-art +Agent AP Resource + | | | + | (1) request | | + | subscribe | | + | token | | + |------------>| | + | | | + | subscribe | | + | token | | + |<------------| | + | | | + | (2) signed request | + | w/ subscribe token | + |------------------------------------->| + | | | + | 200 OK | + |<-------------------------------------| + | | | + | | ... time passes ... | + | | | + | | (3) POST event token. | + | | (+ optional payload) | + | |<-----------------------| + | | | + | | 202 Accepted | + | |----------------------->| + | | | + | (4) event | | + | token + | | + | payload | | + |<------------| | +~~~~ +Figure: AAuth Events Protocol Overview {#fig-overview} + +1. **Subscribe token acquisition (non-normative)**: The agent requests a subscribe token from its AP. The AP generates an `eid`, creates a subscription record, and issues a subscribe token. This interaction is AP-internal and out of scope for this specification. See (#non-normative-ap-agent) for examples. + +2. **Subscription registration**: The agent presents the subscribe token to the resource as the `Signature-Key` JWT on a signed HTTP request to the resource's subscription endpoint. The resource validates the subscribe token, stores the `eid` and the AP's `event_endpoint` (resolved from the AP's metadata), and registers the subscription. + +3. **Event delivery — resource to AP**: When an event fires, the resource issues an event token (a JWT signed by the resource) and POSTs it to the AP's `event_endpoint`, presenting the event token as the `Signature-Key` JWT. The optional request body carries the AsyncAPI-defined payload for the event type. + +4. **Event delivery — AP to agent (non-normative)**: The AP validates the event, looks up the subscription by `eid`, and delivers the event token and any payload to the agent. This step is platform-dependent and out of scope for this specification. See (#non-normative-ap-agent) for examples. + +# AP Metadata {#ap-metadata} + +The AP MUST publish an `event_endpoint` claim in its metadata at `/.well-known/aauth-agent.json` if it supports AAuth Events. The `event_endpoint` is an HTTPS URL at which the AP receives event tokens from resources. + +```json +{ + "issuer": "https://ap.example", + "jwks_uri": "https://ap.example/.well-known/jwks.json", + "event_endpoint": "https://ap.example/events" +} +``` + +The AP MAY update the `event_endpoint` URL at any time. Resources resolve the AP's `event_endpoint` from the AP's metadata (using the `iss` claim in the subscribe token to locate the AP's well-known document) rather than caching it from the subscribe token. + +# Subscribe Token {#subscribe-token} + +## Structure + +A subscribe token is a JWT with `typ: aa-subscribe+jwt`, issued and signed by the AP, with the following claims: + +Header: + +- `alg`: Signing algorithm. EdDSA is RECOMMENDED. Implementations MUST NOT accept `none`. +- `typ`: `aa-subscribe+jwt` +- `kid`: Key identifier (AP's signing key) + +Required payload claims: + +- `iss`: Agent Provider URL. Used by the resource to locate the AP's metadata and `event_endpoint`. +- `dwk`: `aauth-agent.json` — the well-known metadata document name for key discovery ([@!I-D.hardt-httpbis-signature-key]). +- `sub`: Agent identifier. The AAuth agent identifier (`aauth:local@domain`) of the subscribing agent. +- `aud`: Resource URL. The resource that is authorized to deliver events for this subscription. The resource MUST verify that its own URL matches this claim. +- `cnf`: Confirmation claim ([@!RFC7800]) with `jwk` containing the agent's current public signing key. The resource uses this key to verify the HTTP signature on the subscription registration request. +- `eid`: Event ID. An opaque string generated by the AP, unique to the AP. The agent maps the `eid` to its own context (see (#agent-context-mapping)). The resource includes the `eid` in every event token it issues for this subscription. +- `iat`: Issued-at timestamp. +- `exp`: Expiration timestamp. The resource MUST reject subscribe tokens with `exp` in the past. + +Optional payload claims: + +- `max_uses`: A positive integer. If present, the AP MUST NOT accept more than this many event tokens for this `eid`. If absent, the subscription is unlimited. Enforcement is the AP's responsibility; the AP informs the resource of remaining uses in its `202 Accepted` response (see (#event-delivery)). The resource SHOULD track `remaining_uses` to manage subscription state — for example, prompting the agent to re-subscribe when the subscription is exhausted. + +Example subscribe token payload: + +```json +{ + "iss": "https://ap.example", + "dwk": "aauth-agent.json", + "sub": "aauth:k7q3p9n2@ap.example", + "aud": "https://resource.example", + "cnf": { "jwk": { "kty": "OKP", "crv": "Ed25519", "x": "..." } }, + "eid": "evt_8f3k2n9p", + "iat": 1750000000, + "exp": 1750086400, + "max_uses": 1 +} +``` + +## Presentation + +The agent presents the subscribe token as the `Signature-Key` JWT on the subscription registration request, using `scheme=jwt`: + +```http +POST /appointments/waitlist HTTP/1.1 +Host: resource.example +Content-Type: application/json +Signature-Input: sig=("@method" "@authority" + "@path" "signature-key" "content-type");created=1750000000 +Signature: sig=:...signature bytes...: +Signature-Key: sig=jwt; + jwt="eyJhbGciOiJFZERTQSIsInR5cCI6ImFhLXN1Yitqd3QiLCJraWQiOiIuLi4ifQ..." + +{ + "event_types": ["slot.available"] +} +``` + +The subscribe token replaces the agent token as the `Signature-Key` JWT for subscription registration requests. The `cnf.jwk` in the subscribe token provides the key the resource uses to verify the HTTP signature. The subscribe token is structurally analogous to the agent token — both are AP-signed JWTs carrying `cnf.jwk` — distinguished by `typ`. + +## Verification + +The resource MUST verify the subscribe token as follows: + +1. Decode the JWT header. Verify `typ` is `aa-subscribe+jwt`. +2. Verify `dwk` is `aauth-agent.json`. Discover the AP's JWKS via `{iss}/.well-known/{dwk}` per ([@!I-D.hardt-httpbis-signature-key]). Locate the key matching `kid` and verify the JWT signature. +3. Verify `exp` is in the future and `iat` is not in the future. +4. Verify `aud` matches the resource's own URL. +5. Verify `cnf.jwk` matches the key used to sign the HTTP request. +6. Verify `eid` is present and non-empty. + +After verification, the resource stores the subscription record with sufficient information to deliver events — at minimum `{eid, iss}` (the Event ID and the AP's issuer URL). When an event fires, the resource resolves the AP's `event_endpoint` from `{iss}/.well-known/aauth-agent.json` at delivery time, using standard HTTP caching for the well-known document. + +## Agent Context Mapping {#agent-context-mapping} + +The `eid` is the agent's correlation key. The agent maintains a local mapping of `eid` values to internal context — for example, "eid `evt_8f3k2n9p` corresponds to the appointment waitlist for Dr. Smith opened as part of mission `mission_xyz`". This mapping is the agent's own concern and is not defined by this specification. + +# Subscription Registration {#subscription-registration} + +## Public Subscriptions + +For event channels that do not require prior authorization, the agent presents the subscribe token (as the `Signature-Key` JWT) on a signed POST to the resource's subscription endpoint. No additional credential is required. The resource validates the subscribe token per (#subscribe-token) and registers the subscription. + +## Protected Subscriptions {#protected-subscriptions} + +Some event channels require the agent to be authorized before it can register a subscription — for example, subscribing to events for a specific patient's appointments, or events associated with a particular account. In these cases, the resource does not accept subscription registrations from arbitrary agents; only agents that have already been authorized in an earlier interaction may register. + +This specification defines a **pre-authorized subscription URL** pattern for protected subscriptions: + +1. The agent makes an authenticated request to the resource (using an auth token obtained through one of the AAuth Protocol access modes). +2. The resource, if subscription to events is available for the context established by this interaction, returns a **subscription ticket URL** — an HTTPS URL that encodes a short-lived, single-use authorization to register a subscription. The ticket URL is opaque and is valid only for the specific context (agent, operation, and resource state) established in step 1. +3. The agent obtains a subscribe token from its AP. +4. The agent presents the subscribe token (as the `Signature-Key` JWT) on a signed POST to the subscription ticket URL. No additional auth token is required at this step; the authorization is embedded in the URL. The request body MAY include additional parameters as defined by the resource's AsyncAPI channel schema. +5. The resource validates the subscribe token, verifies the ticket in the URL is valid for the calling agent (by checking `sub` in the subscribe token matches the agent that triggered step 1) and has not been used before, and registers the subscription. + +The subscription ticket URL is resource-controlled: the resource issues it, defines its scope and expiry, and enforces its single-use constraint. The ticket is not defined by this specification beyond the pattern above. + +Example response from step 2: + +```json +{ + "status": "unavailable", + "next_available": "2026-08-24", + "waitlist": { + "subscribe_url": "https://resource.example/waitlist/st_9k2m_abc123", + "event_types": ["slot.available"], + "offer_window_seconds": 300 + } +} +``` + +Example subscription registration from step 4: + +```http +POST /waitlist/st_9k2m_abc123 HTTP/1.1 +Host: resource.example +Content-Type: application/json +Signature-Input: sig=("@method" "@authority" + "@path" "signature-key" "content-type");created=1750000000 +Signature: sig=:...signature bytes...: +Signature-Key: sig=jwt; + jwt="eyJhbGciOiJFZERTQSIsInR5cCI6ImFhLXN1Yitqd3QiLCJraWQiOiIuLi4ifQ..." + +{ + "event_types": ["slot.available"] +} +``` + +The HTTP signature covers the request path (including the ticket), cryptographically binding the subscribe token's identity to this specific ticket URL. + +The resource SHOULD include the subscription ticket URL in an AsyncAPI channel parameter ([@AsyncAPI]) so that agents that discover the resource's event capabilities through its AsyncAPI document know to obtain the URL from a prior API response. + +# Event Token {#event-token} + +## Structure + +When an event fires, the resource issues an event token: a JWT signed by the resource with the following claims: + +Header: + +- `alg`: Signing algorithm. EdDSA is RECOMMENDED. +- `typ`: `aa-event+jwt` +- `kid`: Key identifier (resource's signing key) + +Required payload claims: + +- `iss`: Resource URL. +- `dwk`: `aauth-resource.json` — the well-known metadata document name for key discovery ([@!I-D.hardt-httpbis-signature-key]). +- `aud`: Agent identifier (`aauth:local@domain`). The agent MUST verify this matches its own identifier. +- `eid`: Event ID. MUST match the `eid` from the subscribe token for this subscription. The AP uses the `eid` to look up the subscription record and route to the agent. The agent uses the `eid` to look up its local context mapping. +- `iat`: Issued-at timestamp. +- `exp`: Expiration timestamp. The agent MUST NOT act on an event token with `exp` in the past. The meaning of `exp` is event-specific — for time-sensitive events, it encodes the deadline by which the agent must act. + +The event token is the transport and security layer. It carries no event-specific data. Event-specific content is delivered as the POST body alongside the event token (see (#event-delivery)). + +Example event token payload: + +```json +{ + "iss": "https://resource.example", + "dwk": "aauth-resource.json", + "aud": "aauth:k7q3p9n2@ap.example", + "eid": "evt_8f3k2n9p", + "iat": 1750200000, + "exp": 1750200300 +} +``` + +# Event Delivery: Resource to AP {#event-delivery} + +## Request + +When an event fires for an active subscription, the resource posts to the AP's `event_endpoint`, presenting the event token as the `Signature-Key` JWT. The POST body is the AsyncAPI-defined payload for the event type (OPTIONAL — omitted if the event carries no payload): + +```http +POST /events HTTP/1.1 +Host: ap.example +Content-Type: application/json +Signature-Input: sig=("@method" "@authority" + "@path" "signature-key" "content-type" "content-digest");created=1750200000 +Signature: sig=:...resource signing key signature bytes...: +Signature-Key: sig=jwt; + jwt="eyJhbGciOiJFZERTQSIsInR5cCI6ImFhLWV2ZW50K2p3dCIsImtpZCI6Ii4uLiJ9..." + +{ + "event_type": "slot.available", + "slot_time": "2026-07-15T10:00:00Z" +} +``` + +The event token in `Signature-Key` provides the resource's identity (`iss`) and routing and authorization claims (`eid`, `aud`, `exp`). Unlike agent tokens and subscribe tokens, no `cnf.jwk` is needed: the resource has a stable JWKS discoverable from `{iss}/.well-known/{dwk}`, and the AP uses the same key (identified by `kid` in the JWT header) to verify both the JWT signature and the HTTP signature. This is an extension to the `Signature-Key` JWT scheme: when a JWT has `dwk` but no `cnf`, the verifier resolves the HTTP signing key from `{iss}/.well-known/{dwk}` using `kid` rather than from an inline `cnf.jwk`. The request body structure is defined by the resource's AsyncAPI message schema for the event type (see (#event-discovery)). The AP forwards both the event token and the payload body to the agent. + +The resource resolves the AP's `event_endpoint` from `{iss}/.well-known/aauth-agent.json` at delivery time, using standard HTTP caching for the AP's well-known document. + +## AP Validation + +The AP MUST validate the event delivery request as follows: + +1. Extract the event token JWT from the `Signature-Key` header. Verify `typ` is `aa-event+jwt`. +2. Discover the resource's JWKS via `{iss}/.well-known/{dwk}`. Locate the key matching `kid` and verify the JWT signature. +3. Verify the HTTP signature using the same key (matched by `kid`). This applies the `dwk`-without-`cnf` extension to the Signature-Key JWT scheme: the JWT signing key and the HTTP signing key are the same key, discoverable from the resource's well-known document. +4. Look up the subscription record by `eid`. If no active subscription exists for this `eid`, return `404`. +5. Verify `iss` matches the resource recorded at subscription time (the `aud` of the subscribe token for this `eid`). +6. Verify the event token `exp` is in the future. +7. If `max_uses` is set in the subscribe token, verify the use count has not been exceeded. Increment the use count atomically. If the use limit is reached, the AP MAY mark the subscription as complete after delivery. +8. Verify the event token `aud` matches the agent identifier in the subscription record. + +If all checks pass, the AP returns `202 Accepted` and proceeds with delivery to the agent. The AP MUST NOT return `202` before the event has been durably recorded for delivery. If `max_uses` was set in the subscribe token, the AP MUST include a JSON response body with a `remaining_uses` field indicating how many more event tokens the AP will accept for this `eid`: + +```http +HTTP/1.1 202 Accepted +Content-Type: application/json + +{ + "remaining_uses": 0 +} +``` + +When `remaining_uses` is `0`, the subscription is exhausted. The resource SHOULD clean up its subscription record and MAY prompt the agent to re-subscribe on the next interaction. When `max_uses` was not set, the AP returns `202 Accepted` with no body (or an empty JSON object). + +The AP returns `400` for malformed requests, `401` if the resource's HTTP signature cannot be verified, `403` if the resource does not match the subscription's authorized resource, `404` if the `eid` is unknown or the subscription has expired, and `429` if `max_uses` has been exceeded. + +# Event Delivery: AP to Agent {#ap-to-agent} + +How the AP delivers the event token to the agent is platform-dependent and outside the scope of this specification. The AP is the agent's inbox; the internal mechanism is an implementation choice for the AP and agent. + +See (#non-normative-ap-agent) for non-normative examples of AP-to-agent delivery for different platforms. + +## Agent Verification + +Upon receiving an event token (and optional payload) from the AP, the agent MUST: + +1. Decode the JWT header. Verify `typ` is `aa-event+jwt`. +2. Discover the resource's JWKS via `{iss}/.well-known/{dwk}` per ([@!I-D.hardt-httpbis-signature-key]). Verify the JWT signature. +3. Verify `aud` matches the agent's own identifier. +4. Verify `exp` is in the future. If `exp` has passed, the agent SHOULD NOT act on the event (the response window has closed). +5. Look up `eid` in the agent's local context mapping to recover the context associated with this subscription. +6. Deduplicate: if the agent has already processed an event with this `eid` from this `iss`, it SHOULD ignore the duplicate. The `eid` is a natural idempotency key. + +If a payload was included, the agent MAY use it directly. The payload structure is defined by the resource's AsyncAPI message schema for the event type. + +# Event Discovery {#event-discovery} + +Resources describe their event capabilities using AsyncAPI ([@AsyncAPI]) as an AAuth R3 vocabulary ([@!I-D.hardt-aauth-r3]). + +## R3 Vocabulary Identifier + +The vocabulary identifier for AAuth Events is: + +``` +urn:aauth:vocabulary:asyncapi +``` + +Resources that support AAuth Events SHOULD declare this vocabulary in their AAuth resource metadata: + +```json +{ + "issuer": "https://resource.example", + "r3_vocabularies": { + "urn:aauth:vocabulary:openapi": "/openapi.json", + "urn:aauth:vocabulary:asyncapi": "/asyncapi.json" + } +} +``` + +## AsyncAPI Document + +The resource's AsyncAPI document describes: + +- **Channels**: Event streams the agent may subscribe to. Channels MAY use parameterized addresses (e.g., `/waitlist/{subscriptionTicket}`) when the subscription endpoint URL is dynamic (see (#protected-subscriptions)). +- **Operations**: `receive` operations on channels, with the security requirement and message schema. +- **Messages**: The payload schema for each event type. The AsyncAPI payload schema describes the `payload` field in the event delivery POST body (see (#event-delivery)). The AAuth event token envelope (`iss`, `aud`, `eid`, `exp`) is implicit and not part of the AsyncAPI schema. +- **Security schemes**: The AAuth subscribe token security scheme. + +## Security Scheme + +Resources MUST declare the AAuth subscribe token security scheme as follows: + +```yaml +securitySchemes: + aauth_subscribe: + type: http + scheme: aauth-subscribe + description: > + AAuth Subscribe Token (typ: aa-subscribe+jwt), issued by the agent's Agent + Provider, presented as the Signature-Key JWT with HTTP Message Signatures. + See draft-hardt-aauth-events. +``` + +Operations that require only a subscribe token declare: + +```yaml +security: + - aauth_subscribe: [] +``` + +Operations that require a pre-authorized subscription URL (see (#protected-subscriptions)) have no security scheme on the subscription endpoint itself — the subscription ticket in the URL carries the authorization. The resource SHOULD annotate such channels with a description noting that the subscription URL is obtained from a prior authenticated API call. + +## Example AsyncAPI Document + +```yaml +asyncapi: 3.0.0 +info: + title: Appointments Events + version: 1.0.0 + +channels: + waitlistPublic: + address: /appointments/waitlist/public + messages: + slotAvailable: + $ref: '#/components/messages/SlotAvailable' + + waitlistProtected: + address: /appointments/waitlist/{subscriptionTicket} + description: > + Subscription URL returned by POST /appointments when no slot is + immediately available and the calling agent is authorized for + waitlist access. The subscriptionTicket is embedded in the URL + and carries the authorization context. + parameters: + subscriptionTicket: + description: Single-use ticket from the POST /appointments response. + messages: + slotAvailable: + $ref: '#/components/messages/SlotAvailable' + +operations: + subscribePublicWaitlist: + action: receive + channel: + $ref: '#/channels/waitlistPublic' + security: + - aauth_subscribe: [] + + subscribeProtectedWaitlist: + action: receive + channel: + $ref: '#/channels/waitlistProtected' + +components: + messages: + SlotAvailable: + contentType: application/jwt + payload: + type: object + properties: + event_type: + type: string + const: slot.available + slot_time: + type: string + format: date-time + doctor_id: + type: string + required: + - event_type + - slot_time + + securitySchemes: + aauth_subscribe: + type: http + scheme: aauth-subscribe + description: AAuth Subscribe Token as Signature-Key JWT +``` + +# Security Considerations + +## Subscribe Token Scope + +The `aud` claim in the subscribe token restricts which resource may deliver events to the AP for this `eid`. If a resource attempts to deliver events for an `eid` issued to a different resource, the AP MUST reject the request (#event-delivery). This prevents a compromised resource from hijacking another resource's subscription channel. + +## Event Token Forgery + +Event tokens are signed by the resource using the resource's own signing key. The agent verifies the event token against the resource's JWKS ([@!I-D.hardt-httpbis-signature-key]). A party without the resource's private key cannot forge a valid event token. There are no shared secrets in AAuth Events. + +## Replay Prevention + +The AP enforces `max_uses` per `eid` and rejects event tokens with `exp` in the past. The agent additionally deduplicates on `eid` from the same `iss` (#agent-verification). These two layers prevent replay: a captured event token cannot be re-delivered once the AP has tracked its delivery and the agent has processed it. + +## Subscribe Token Replay at Registration + +A subscribe token with a valid `exp` could in principle be presented to the resource's subscription endpoint more than once. The `eid` is the deduplication key: the resource SHOULD reject subscription registration requests for an `eid` it has already registered. Single-use enforcement of the subscription ticket URL (in protected subscriptions) provides an additional constraint. + +## Pre-Authorized Subscription URL Security + +The subscription ticket URL (see (#protected-subscriptions)) encodes authorization from a prior authenticated context. Resources MUST ensure that subscription tickets are: + +- Short-lived (expiry appropriate to the expected delay between issuing and using the ticket). +- Single-use (the resource invalidates the ticket on first successful subscription registration). +- Bound to the agent that triggered the prior interaction (the resource MUST verify that `sub` in the subscribe token matches the agent that established the ticket). + +## AP as Delivery Intermediary + +The AP sees every event token delivered to an agent. The AP validates the event token's `iss`, `aud`, and `eid` claims but does not need to inspect resource-specific payload claims. APs SHOULD document their data retention policies for event tokens. + +## Resource Enumeration + +A resource that exposes its AsyncAPI document publicly reveals what event types it emits. This may be intentional (public API). Resources that wish to restrict event type discovery MAY gate their AsyncAPI document with AAuth authentication. + +# Privacy Considerations + +## Agent Identifier Stability + +The `sub` claim in the subscribe token carries the agent's stable identifier. Resources that receive subscribe tokens can correlate an agent's subscription activity over time. This is the intended property — the resource needs to know which agent subscribed. Agents and APs should be aware that subscription registrations leave a record at the resource. + +## Event Content + +The event token carries no event-specific data — it is the security and routing envelope only. Event-specific content travels in the `payload` field of the POST body (see (#event-delivery)), which is also visible to the AP during routing. Resources SHOULD NOT include sensitive personal data in the payload beyond what is necessary for the agent to evaluate relevance. Sensitive details SHOULD be fetched by the agent from the resource's data API using a current auth token. + +# IANA Considerations + +## JWT Type Values + +This specification defines the following JWT `typ` header parameter values, to be registered in the IANA "JSON Web Token Types" registry: + +- `aa-subscribe+jwt`: AAuth Subscribe Token. +- `aa-event+jwt`: AAuth Event Token. + +## AAuth R3 Vocabulary Identifiers + +This specification defines the following R3 vocabulary identifier: + +- `urn:aauth:vocabulary:asyncapi`: AAuth AsyncAPI event vocabulary. + +# Implementation Status + +*Note: This section is to be removed before publishing as an RFC.* + +TBD + +# Document History + +*Note: This section is to be removed before publishing as an RFC.* + +- draft-hardt-aauth-events-00 + - Initial draft. + +# Acknowledgments + +TBD. + +{backmatter} + +# Design Rationale {#design-rationale} + +This appendix explains the key design decisions in AAuth Events and the alternatives considered. + +## Why the AP Is the Delivery Intermediary + +Agents are workloads, not servers. They spin up, execute, and terminate. They run behind NAT, inside containers, or on mobile devices. They have no stable public endpoint. + +Every existing push delivery mechanism (webhooks, WebSub, CIBA ping/push mode, W3C Web Push) assumes the subscriber has a stable HTTP endpoint. W3C Web Push is the closest analog to what AAuth Events does — it uses a browser push service (Google/Apple/Mozilla) as the subscriber's stable address. AAuth Events uses the AP in this role, with two improvements: the AP already has a trust relationship with the agent (it issued the agent's identity token), and the subscriber's identity is cryptographic (not just an opaque push service subscription). + +The AP-as-inbox pattern mirrors how email works: you do not need to be online when someone sends you mail. The mail server is the stable address. AAuth Events gives agents the same property for event delivery. + +## Why the Subscribe Token Is the Signature-Key JWT + +The subscribe token simultaneously serves two functions: it proves the agent's identity (via `cnf.jwk` + HTTP signature) and registers the subscription (via `eid`, `aud`, `exp`). Presenting it as the `Signature-Key` JWT means a single signed HTTP request to the subscription endpoint accomplishes both without a separate credential or header. + +This is structurally analogous to the agent token — both are AP-signed JWTs with `cnf.jwk`, distinguished by `typ`. The resource's verification path is the same whether it is processing an identity-based request with an agent token or a subscription registration with a subscribe token. + +The alternative — a separate header or body parameter carrying the subscribe token alongside the normal agent token — was rejected because it requires two credentials where the subscribe token alone is sufficient. + +## Why `aud` in the Subscribe Token Is the Resource + +The `aud` claim restricts event delivery authorization to a specific resource. Only the resource named in `aud` may deliver events for this `eid` to the AP. This prevents: + +- A compromised resource from injecting events into another agent's subscription channels. +- The AP from accepting events from unexpected callers. + +The AP enforces this by matching the calling resource (identified by its HTTP signature) against the `aud` in the subscribe token stored in the subscription record. + +## Why `exp` Is the JWT Validity Period, Not the Subscription Lifetime + +Subscription lifetime is a negotiation between the agent and the resource at registration time. The resource has its own policy on maximum subscription duration. These durations can be days or months and are resource-specific. + +The subscribe token's `exp` is the standard JWT validity window — how long the resource may accept this token for registration. Conflating JWT validity with subscription lifetime would either force a very long-lived token (security concern: replay window) or a very short subscription (UX concern: subscriptions expire before they're useful). + +The resource stores the subscription record with whatever lifetime the agent and resource negotiate at registration. The subscribe token is a registration credential, not a subscription policy document. + +## Why `max_uses` Is in the Subscribe Token + +`max_uses` is the AP's throttle on how many event tokens it will accept for a given `eid`. It is AP-enforced, not resource-enforced. Placing it in the subscribe token — which the AP issued and controls — makes it AP-policy without requiring a separate AP configuration step. + +For single-shot events (confirm this reservation), `max_uses: 1` ensures the AP accepts exactly one event. For ongoing subscriptions, `max_uses` is omitted (unlimited). When `max_uses` is absent, there is no sentinel value — absence means unlimited, avoiding any need for a special value such as -1. + +The AP informs the resource of remaining uses in the `202 Accepted` response body after each delivery. The resource SHOULD use `remaining_uses: 0` as the signal to clean up its subscription record and prompt the agent to re-subscribe. This keeps the AP as the enforcement point while giving the resource the state it needs to manage the subscription lifecycle. + +## Why the Event Token Is the Transport Layer, Not the Data Layer + +The event token carries only what is needed for security, routing, and correlation: `iss`, `aud`, `eid`, `exp`. It is the cryptographic layer — the AP uses it to authenticate the resource, look up the subscription, and verify the delivery is authorized. The agent uses it to verify authenticity and look up its context via `eid`. + +Event-specific data travels as a separate `payload` in the same POST body. The AsyncAPI message schema for the event type defines the payload structure. This separation keeps the JWT minimal and avoids embedding event data in a signed-but-not-encrypted envelope. For events where the agent needs full details beyond the payload, it fetches them from the resource's data API using a current auth token. + +## Why `exp` in the Event Token Is the Response Window + +The `exp` claim in the event token defines how long the agent has to respond to the event. Its meaning is event-specific: for a waitlist slot, it is the deadline by which the agent must claim the slot; for a shipping confirmation, it may be a much longer acknowledgment window. + +The AP delivers events in near real-time. If the AP cannot deliver an event before its `exp`, the agent should not act on it (the response window has closed). The agent verifies `exp` before acting. + +## Why Protected Subscriptions Use a Pre-Authorized URL + +For protected event channels, the resource needs to verify that the subscribing agent has been authorized in a prior interaction before accepting the subscription. The naive approach — requiring both an auth token and a subscribe token on the subscription call — is awkward because the two tokens serve different purposes and the auth token conveyance alongside a `Signature-Key` subscribe token has no established AAuth pattern. + +The pre-authorized subscription URL pattern solves this cleanly: authorization is captured in the prior authenticated interaction, and the resource returns a ticket URL that encodes this context. The agent then presents only the subscribe token at the ticket URL. The HTTP signature covers the URL path (including the ticket), binding the subscribe token's identity to this specific authorization context. + +This mirrors established patterns (OAuth authorization codes, S3 presigned URLs) while preserving the AAuth Events invariant: all subscription endpoints accept only the subscribe token as the `Signature-Key` JWT. + +## Why AsyncAPI Is the Discovery Vocabulary + +AsyncAPI is the de facto standard for describing event-driven APIs. It describes channels, message schemas, security requirements, and (via parameterized addresses) dynamic subscription endpoints. The AAuth R3 vocabulary framework already accommodates multiple vocabularies per resource — AsyncAPI sits naturally alongside OpenAPI for synchronous operations. + +The target reader of an AAuth resource's AsyncAPI document is an AAuth-capable agent, not generic AsyncAPI tooling. This means the `aauth_subscribe` security scheme (type: `http`, scheme: `aauth-subscribe`) does not need to be understood by Swagger UI or code generators — it is a declaration for the agent's benefit, interpreted per this specification. + +## Comparison to Existing Patterns + +| | AAuth Events | Webhooks | WebSub | CIBA (ping) | Web Push | +|---|---|---|---|---|---| +| Receiver needs public URL | No | Yes | Yes | Yes | No | +| Caller identity | Cryptographic (resource key) | HMAC shared secret | None | Client creds | Push service | +| Subscriber identity at resource | Agent identifier (`sub`) | None | None | Client ID | Subscription ID | +| Per-operation subscription | Yes | No (account-level) | No | Yes | No | +| General event types | Yes | Yes | Yes | No (auth only) | Yes | +| Standard description format | AsyncAPI (R3) | Proprietary | Atom/RSS | N/A | None | + +# Non-Normative AP-to-Agent Delivery Examples {#non-normative-ap-agent} + +How the AP delivers an event token to an agent is platform-dependent and not specified by this document. The following examples illustrate common patterns, paralleling the approach taken in [@?I-D.hardt-aauth-bootstrap] for agent token acquisition. + +## Workload Agents + +A workload agent (running in a cloud function, container, or batch job) may poll the AP for pending event tokens on startup, using an AP-internal endpoint. The AP acts as a durable inbox — storing event tokens until the workload polls. The workload validates and processes pending events before beginning its primary task. + +## Mobile Agents + +A mobile agent may receive events via the platform's native push notification infrastructure (APNs on iOS, FCM on Android). The AP holds a push token registered by the agent at enrollment time and delivers event tokens to the agent via push notification. The agent wakes on receipt, fetches the full event token from the AP if needed, and processes it. + +## Web Agents + +A web agent with a persistent session may receive events via a server-sent event (SSE) or WebSocket connection that the agent maintains to the AP. The AP streams event tokens over this connection as they arrive. + +## Self-Hosted Agents + +A self-hosted agent may receive events in two ways depending on whether it manages its own AP or delegates to an external one. + +If the agent acts as its own AP ([@?I-D.hardt-aauth-bootstrap]), it may expose an internal event endpoint. Events are delivered directly to this endpoint by the resource — the AP and agent are collocated. + +If the agent uses an external AP service, it maintains an outbound persistent connection (SSE, WebSocket, or a similar mechanism) to the AP's inbox service. The AP delivers event tokens and payloads over this connection as they arrive. The self-hosted agent does not need a public inbound endpoint — the outbound connection to the AP is sufficient. + + diff --git a/aauth-spec/v09/draft-hardt-aauth-r3.md b/aauth-spec/v09/draft-hardt-aauth-r3.md new file mode 100644 index 0000000..7252098 --- /dev/null +++ b/aauth-spec/v09/draft-hardt-aauth-r3.md @@ -0,0 +1,665 @@ +%%% +title = "AAuth Rich Resource Requests (R3)" +abbrev = "AAuth-R3" +ipr = "trust200902" +area = "Security" +workgroup = "TBD" +keyword = ["agent", "authorization", "http", "resource"] + +[seriesInfo] +status = "standard" +name = "Internet-Draft" +value = "draft-hardt-aauth-r3-latest" +stream = "IETF" + +date = 2026-03-24T00:00:00Z + +[[author]] +initials = "D." +surname = "Hardt" +fullname = "Dick Hardt" +organization = "Hellō" + [author.address] + email = "dick.hardt@gmail.com" + +%%% + + + + AAuth Protocol + + Hellō + + + + + + + + AAuth Events + + Hellō + + + + + + +.# Abstract + +This document defines AAuth Rich Resource Requests (R3), an extension to the AAuth Protocol ([@!I-D.hardt-aauth-protocol]) that enables structured, vocabulary-based authorization for resource access. Resources publish R3 documents (content-addressed authorization definitions) and advertise vocabularies describing their operations. Agents request access using those vocabularies. Auth tokens carry granted operations in the same vocabulary format, enabling resources to enforce authorization directly from the token. R3 provides human-displayable context for consent decisions and content-addressed audit provenance via the `r3_s256` hash in auth tokens. + +.# Discussion Venues + +*Note: This section is to be removed before publishing as an RFC.* + +This document is part of the AAuth specification family. Source for this draft and an issue tracker can be found at https://github.com/dickhardt/AAuth. + +{mainmatter} + +# Introduction + +**Status: Exploratory Draft** + +The AAuth Protocol ([@!I-D.hardt-aauth-protocol]) defines resource tokens as the mechanism by which resources declare what authorization is needed to access them, and scope strings as the primary way to express what operations are available. Scopes are sufficient for simple, well-known access patterns but are limited in three respects: + +1. **Human comprehension.** Scope strings like `calendar:write` are not self-describing to users or auth agents making approval decisions. + +2. **Machine precision.** Scopes do not express which specific operations are being authorized or distinguish operations that need per-call approval. + +3. **Audit completeness.** Scopes do not identify which specific version of an authorization definition was in effect at the time of approval. + +R3 addresses these by introducing: + +- **Vocabularies** that describe a resource's operations in terms the agent already understands (MCP tools, OpenAPI operations, gRPC methods, etc.) +- **R3 documents**: structured, content-addressed authorization definitions published by the resource and fetched by the AS +- **Vocabulary-based grants** in auth tokens, so resources can enforce authorization directly from claims they understand + +# Conventions and Definitions + +{::boilerplate bcp14-tagged} + +# Terminology + +- **Vocabulary**: A defined scheme for expressing resource operations. Each vocabulary corresponds to an API description format (MCP, OpenAPI, gRPC, GraphQL, AsyncAPI, WSDL, OData). Resources advertise which vocabularies they support; agents use them to request access. +- **R3 Document**: A JSON document published by a resource, describing the operations it provides and the consequences of granting access. Identified by the SHA-256 hash of its content. Fetched by both the PS (for user consent using `display` fields) and the AS (for policy evaluation using `operations`); not accessible to agents. +- **R3 URI (`r3_uri`)**: A URI identifying an R3 document. Included in a resource token. +- **R3 Hash (`r3_s256`)**: A SHA-256 hash of the R3 document, base64url-encoded without padding. Included alongside `r3_uri` in the resource token and the auth token. + +# Vocabularies + +Resources advertise their supported vocabularies in well-known metadata. Each vocabulary maps to an API description format that agents already know how to discover and parse. + +## Resource Metadata Extensions + +R3 extends the `/.well-known/aauth-resource.json` document defined in AAuth Protocol ([@!I-D.hardt-aauth-protocol]): + +```json +{ + "resource": "https://calendar.example.com", + "r3_vocabularies": { + "urn:aauth:vocabulary:mcp": "https://calendar.example.com/mcp", + "urn:aauth:vocabulary:openapi": "https://calendar.example.com/openapi.json" + } +} +``` + +**`r3_vocabularies`** (OPTIONAL). A JSON object mapping vocabulary URIs to their discovery endpoints. Keys MUST be vocabulary URIs from the `urn:aauth:vocabulary:` namespace for standard vocabularies defined in this document, or third-party URI namespaces for proprietary vocabularies. Values are vocabulary-specific discovery endpoints (the MCP server URL, the OpenAPI spec URL, the gRPC reflection endpoint, etc.). A resource MAY advertise multiple vocabularies simultaneously. + +## Standard Vocabularies + +This document defines seven standard vocabularies. Third parties MAY define additional vocabularies using their own URI namespaces. Each vocabulary defines: the vocabulary URI, the structure of operation requests, how the resource maps operations to R3 documents, and the discovery endpoint. + +Standard vocabularies use the `urn:aauth:vocabulary:` namespace. + +### MCP Vocabulary (`urn:aauth:vocabulary:mcp`) {#mcp-vocabulary} + +For resources that expose an MCP server. The discovery endpoint is the MCP server URL. Agents discover available tool names via MCP tool discovery. + +Each operation entry contains: + +- **`tool`** (REQUIRED). The MCP tool name as advertised by the MCP server's tool discovery. + +```json +{ + "vocabulary": "urn:aauth:vocabulary:mcp", + "operations": [ + { "tool": "create_calendar_event" }, + { "tool": "modify_calendar_event" } + ] +} +``` + +### OpenAPI Vocabulary (`urn:aauth:vocabulary:openapi`) {#openapi-vocabulary} + +For resources that expose an OpenAPI-described HTTP API. The discovery endpoint is the OpenAPI specification URL. Agents discover available operations by fetching and parsing the spec. + +Each operation entry contains: + +- **`operationId`** (REQUIRED). The `operationId` as defined in the OpenAPI specification. + +```json +{ + "vocabulary": "urn:aauth:vocabulary:openapi", + "operations": [ + { "operationId": "createEvent" }, + { "operationId": "updateEvent" } + ] +} +``` + +### gRPC Vocabulary (`urn:aauth:vocabulary:grpc`) {#grpc-vocabulary} + +For resources that expose a gRPC server. The discovery endpoint is the gRPC server reflection endpoint (supporting `grpc.reflection.v1.ServerReflection`) or a hosted `.proto` file URL. + +Each operation entry contains: + +- **`method`** (REQUIRED). The fully qualified gRPC method name in the form `package.ServiceName/MethodName`. + +```json +{ + "vocabulary": "urn:aauth:vocabulary:grpc", + "operations": [ + { "method": "calendar.CalendarService/CreateEvent" }, + { "method": "calendar.CalendarService/UpdateEvent" } + ] +} +``` + +### GraphQL Vocabulary (`urn:aauth:vocabulary:graphql`) {#graphql-vocabulary} + +For resources that expose a GraphQL API. The discovery endpoint is the GraphQL endpoint. Agents discover available operations via GraphQL introspection (`__schema` query). + +Each operation entry contains: + +- **`operation`** (REQUIRED). The GraphQL operation name. MUST be a named query, mutation, or subscription. +- **`type`** (REQUIRED). One of `query`, `mutation`, or `subscription`. + +```json +{ + "vocabulary": "urn:aauth:vocabulary:graphql", + "operations": [ + { "operation": "CreateCalendarEvent", "type": "mutation" }, + { "operation": "GetCalendarEvents", "type": "query" } + ] +} +``` + +### AsyncAPI Vocabulary (`urn:aauth:vocabulary:asyncapi`) {#asyncapi-vocabulary} + +For resources that emit events described by AsyncAPI. The discovery endpoint is the AsyncAPI specification URL. + +- **`operationId`** (REQUIRED). The `operationId` as defined in the AsyncAPI specification. +- **`action`** (OPTIONAL). The AsyncAPI action type: `send` or `receive`. Agents subscribing to events use `receive`. + +When a resource grants an agent an AsyncAPI subscription operation via R3, the actual subscription registration and event delivery use the AAuth Events protocol ([@?I-D.hardt-aauth-events]). The resource issues a subscription ticket URL in response to the authenticated request; the agent then completes subscription registration using a subscribe token per AAuth Events. + +```json +{ + "vocabulary": "urn:aauth:vocabulary:asyncapi", + "operations": [ + { "operationId": "publishCalendarUpdate", "action": "send" }, + { "operationId": "receiveCalendarEvent", "action": "receive" } + ] +} +``` + +### WSDL Vocabulary (`urn:aauth:vocabulary:wsdl`) {#wsdl-vocabulary} + +For resources that expose a SOAP/WSDL-described web service. The discovery endpoint is the WSDL document URL. + +Each operation entry contains: + +- **`operation`** (REQUIRED). The operation name as defined in the WSDL `portType` or `binding`. +- **`service`** (OPTIONAL). The WSDL service name, for disambiguation when multiple services expose the same operation name. + +```json +{ + "vocabulary": "urn:aauth:vocabulary:wsdl", + "operations": [ + { "operation": "CreateCalendarEvent", "service": "CalendarService" } + ] +} +``` + +### OData Vocabulary (`urn:aauth:vocabulary:odata`) {#odata-vocabulary} + +For resources that expose an OData service. The discovery endpoint is the OData service root URL. Agents discover entity sets, functions, and actions via the `$metadata` document. + +Each operation entry contains: + +- **`operation`** (REQUIRED). An entity set name, a bound function (`EntitySet/FunctionName`), or a bound action (`EntitySet/ActionName`). +- **`methods`** (OPTIONAL). An array of HTTP methods for entity set CRUD (e.g., `["GET", "POST"]`). Omitted for bound functions and actions. + +```json +{ + "vocabulary": "urn:aauth:vocabulary:odata", + "operations": [ + { "operation": "Events", "methods": ["GET", "POST", "PATCH"] }, + { "operation": "Events/SendCancellation" } + ] +} +``` + +# Authorization Endpoint Extensions {#authorization-endpoint-extensions} + +R3 extends the authorization endpoint defined in AAuth Protocol ([@!I-D.hardt-aauth-protocol]) with an `r3_operations` request parameter. When an agent wants to declare intended operations using a resource's vocabulary, it includes `r3_operations` in the authorization endpoint request body. + +## Request + +The agent sends `r3_operations` in the authorization endpoint request: + +```http +POST /authorize HTTP/1.1 +Host: calendar.example.com +Content-Type: application/json +Signature-Input: sig=("@method" "@authority" "@path" "signature-key");created=1741824000 +Signature: sig=:...signature bytes...: +Signature-Key: sig=jwt;jwt="eyJhbGc..." + +{ + "r3_operations": { + "vocabulary": "urn:aauth:vocabulary:openapi", + "operations": [ + { "operationId": "createEvent" }, + { "operationId": "updateEvent" } + ] + } +} +``` + +**`r3_operations`** (OPTIONAL). An object containing: + +- **`vocabulary`** (REQUIRED). A URI identifying the vocabulary. MUST be supported by the resource as advertised in `r3_vocabularies`. +- **`operations`** (REQUIRED). An array of operation requests. Structure is vocabulary-specific; see {{mcp-vocabulary}} through {{odata-vocabulary}}. + +When `r3_operations` is present, the resource maps the declared operations to an appropriate R3 document and includes `r3_uri` and `r3_s256` in the resource token. When `r3_operations` is absent, the resource MAY still include R3 claims in the resource token based on its own policy. + +## Response + +The resource returns a resource token as defined in AAuth Protocol ([@!I-D.hardt-aauth-protocol]), extended with R3 claims: + +```json +{ + "resource_token": "eyJhbGciOiJFUzI1NiJ9..." +} +``` + +The resource token contains `r3_uri` and `r3_s256` identifying the R3 document that covers the requested operations. The resource's internal mapping from operations to R3 documents is opaque to the agent. + +## Operations Spanning Multiple Definitions {#operations-spanning-definitions} + +A resource MAY organize its authorization definitions into multiple R3 documents internally — for example, one document per scope or operation group. A single resource token carries exactly one `r3_uri`/`r3_s256` pair, and the resulting auth token therefore pins exactly one R3 document. + +When an agent's `r3_operations` request includes operations that the resource maps to more than one of its internal definitions, the resource MUST compose a single R3 document that covers all of the requested operations and reference that composed document in the resource token: + +- **`operations`** is the union of the requested operations, expressed in the request's vocabulary. +- **`display`** describes the combined access. The resource MAY merge the `display` sections of the underlying definitions (for example, concatenating `implications` and unioning `data_accessed`) or author a purpose-built summary for the combination. + +The composed document is served at a fresh content-addressed `r3_uri` exactly like any other R3 document (see {{content-addressing}}): the resource builds it on the fly and persists the serialized bytes so the hash remains stable across the AS and PS fetches. Because R3 documents are content-addressed, an identical combination of operations reduces to the same hash and MAY be cached and reused across requests. + +This composition is opaque to the agent. The agent sees one `r3_uri`/`r3_s256` in the resource token regardless of how many internal definitions the requested operations were drawn from, and the auth token's `r3_granted` and `r3_conditional` claims express the granted operations against that single composed document. + +# R3 Document {#r3-document} + +An R3 document is a JSON object published by the resource at a URI. It describes the authorization semantics for a class of access: what operations are covered (in vocabulary format), what the access means in human terms, and what consequences it carries. + +The document MUST be served over HTTPS. The resource MUST require a valid HTTP Message Signature on requests to R3 document URIs, and MUST reject requests that are not signed by the resource's AS. Agents cannot fetch R3 documents. + +```json +{ + "version": "2", + "vocabulary": "urn:aauth:vocabulary:mcp", + "operations": [ + { "tool": "create_calendar_event" }, + { "tool": "modify_calendar_event" } + ], + "display": { + "summary": "Create and modify events on your work calendar", + "implications": "Meetings can be scheduled or rescheduled. Existing events can be modified.", + "data_accessed": "Event titles, times, attendees, and descriptions in the work calendar", + "irreversible": "Sent meeting invitations cannot be unsent" + } +} +``` + +## Fields + +**`version`** (RECOMMENDED). A string identifying the version of this R3 document. The combination of `r3_uri` + SHA-256 hash provides content-addressing independent of this field; `version` is for human readability. + +**`vocabulary`** (REQUIRED). The vocabulary URI identifying how operations are expressed. MUST match one of the vocabularies the resource advertises in `r3_vocabularies`. + +**`operations`** (REQUIRED). An array of operations covered by this R3 document, using the vocabulary-specific structure defined in {{mcp-vocabulary}} through {{odata-vocabulary}}. This is the same format used in the agent's `r3_operations` request and in the auth token's `r3_granted` and `r3_conditional` claims. + +**`display`** (RECOMMENDED). Human-readable descriptions of the consequences of granting this access. The resource describes what *it* does, not what the agent intends: + +- `summary` (REQUIRED if `display` present). A short plain-language description suitable for a consent UI or auth agent. +- `implications` (OPTIONAL). Side effects of granting this access: emails sent, records modified, costs incurred. +- `data_accessed` (OPTIONAL). What data becomes visible to the caller. +- `irreversible` (OPTIONAL). Plain-language description of actions that cannot be undone. + +## Content Addressing {#content-addressing} + +The R3 hash (`r3_s256`) is computed as the SHA-256 hash of the bytes of the R3 document as served by the resource, base64url-encoded without padding. + +The resource's serialization is the document. There is no canonicalization step — verifiers hash the bytes received over the wire, not a normalized form. Resources MUST serialize the R3 document once and serve those exact bytes verbatim on every request for the same `r3_uri`. Re-serialization between hash computation and serving (e.g. middleware that parses and re-stringifies JSON, CDN minification, response framework helpers that reorder keys) will produce different bytes and break hash verification. Resources that build R3 documents on the fly SHOULD persist the serialized bytes (e.g. in a key-value store keyed by `r3_uri` or `r3_s256`) rather than re-build the document per request. + +The `r3_s256` hash is the document's identity, not the URI. The AS caches documents by hash. If a resource updates the document at the same URI, existing auth tokens still reference the previous hash (which the AS has cached). New resource tokens reference the new hash. This enables: + +- **Infinite caching by the AS.** A document that verifies against its hash need never be re-fetched. +- **Permanent audit records.** An auth token carrying `r3_s256` identifies the exact authorization semantics that were approved, regardless of subsequent changes at the same URI. + +## Resource Token Extensions + +R3 extends the resource token defined in AAuth Protocol ([@!I-D.hardt-aauth-protocol]) (a JWT with `typ: resource+jwt`) with two additional payload claims. When a resource includes R3 information, it MUST include both. + +Base claims (from AAuth Protocol): +- `iss`: Resource URL +- `dwk`: `aauth-resource.json` +- `aud`: Auth server URL +- `jti`: Unique token identifier +- `agent`: Agent identifier +- `agent_jkt`: JWK Thumbprint of the agent's signing key +- `iat`: Issued at timestamp +- `exp`: Expiration timestamp +- `scope`: Requested scopes (optional) + +R3 extension claims: +- **`r3_uri`** (REQUIRED for R3): The URI where the AS can fetch the R3 document. The AS authenticates itself using an HTTP Message Signature. +- **`r3_s256`** (REQUIRED for R3): The SHA-256 hash of the R3 document at `r3_uri`, base64url-encoded without padding. + +```json +{ + "typ": "resource+jwt", + "alg": "EdDSA", + "kid": "resource-key-1" +} +``` + +```json +{ + "iss": "https://calendar.example.com", + "dwk": "aauth-resource.json", + "aud": "https://as.example.com", + "jti": "rt-8f3a2b", + "agent": "assistant@agent.example", + "agent_jkt": "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs", + "r3_uri": "https://calendar.example.com/r3/a1b2c3d4", + "r3_s256": "aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789abcd", + "iat": 1741824000, + "exp": 1741824300 +} +``` + +Resource tokens MAY include both `scope` (as defined in AAuth Protocol ([@!I-D.hardt-aauth-protocol])) and R3 claims. When both are present, the AS MUST enforce both independently. + +# R3 Processing + +Both the PS and the AS fetch R3 documents, but for different purposes: + +- **The PS** fetches R3 to present the `display` section to the user during consent — summary, implications, data accessed, irreversibility. The PS uses this information to determine whether the request fits the mission scope and to obtain informed user consent. +- **The AS** fetches R3 to evaluate `operations` for policy decisions and to populate `r3_granted` and `r3_conditional` in the auth token. + +Both independently verify `r3_s256` against the fetched document. Because R3 documents are content-addressed, both can cache aggressively by hash. + +## AS Processing + +When the AS receives a resource token containing `r3_uri` and `r3_s256`, it MUST: + +1. Validate the resource token signature per AAuth Protocol ([@!I-D.hardt-aauth-protocol]). +2. Fetch the R3 document at `r3_uri`. The AS MAY use a cached copy if the cache entry was stored with the same `r3_s256` value. +3. Compute the SHA-256 hash of the bytes received and compare it to `r3_s256`. If the hashes do not match, the AS MUST reject the resource token. +4. Record `r3_uri` and `r3_s256` in its audit log alongside the token issuance event, the agent identifier, and the timestamp. +5. Use the `operations` section for policy evaluation. +6. Include `r3_uri`, `r3_s256`, `r3_granted`, and (if applicable) `r3_conditional` in the issued auth token. + +## Caching + +Because R3 documents are content-addressed, the AS MAY cache them by `r3_s256` to avoid redundant fetches. When serving a cached entry, the AS MUST verify that the stored document produces the expected hash. The AS is not required to retain R3 documents beyond their immediate use in token issuance — the AS's audit log records `r3_uri` and `r3_s256`, which is sufficient for later verification by re-fetching. + + +# Auth Token Extensions + +R3 extends the auth token defined in AAuth Protocol ([@!I-D.hardt-aauth-protocol]) (a JWT with `typ: auth+jwt`) with claims for audit provenance and vocabulary-based grants. The resource can enforce authorization directly from these claims. + +Base claims (from AAuth Protocol): +- `iss`: Auth server URL +- `dwk`: `aauth-issuer.json` +- `aud`: Resource URL +- `jti`: Unique token identifier +- `agent`: Agent identifier +- `cnf`: Confirmation claim with `jwk` containing the agent's public key +- `iat`: Issued at timestamp +- `exp`: Expiration timestamp +- `sub`: User identifier (conditional) +- `scope`: Authorized scopes (conditional) + +R3 extension claims: +- **`r3_uri`** (REQUIRED for R3): The URI of the R3 document that was in effect at approval time. +- **`r3_s256`** (REQUIRED for R3): The SHA-256 hash of that R3 document. +- **`r3_granted`** (REQUIRED for R3): Operations the AS fully authorized. The resource serves these immediately. +- **`r3_conditional`** (OPTIONAL): Operations authorized in principle but requiring per-call approval based on the specific parameters the agent provides. + +```json +{ + "typ": "auth+jwt", + "alg": "EdDSA", + "kid": "as-key-1" +} +``` + +```json +{ + "iss": "https://as.example.com", + "dwk": "aauth-issuer.json", + "aud": "https://calendar.example.com", + "jti": "at-9d4c1e", + "agent": "assistant@agent.example", + "sub": "user:alice@example.com", + "cnf": { "jwk": { "kty": "OKP", "crv": "Ed25519", "x": "NzbLsXh8uDCcd..." } }, + "r3_uri": "https://calendar.example.com/r3/a1b2c3d4", + "r3_s256": "aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789abcd", + "r3_granted": { + "vocabulary": "urn:aauth:vocabulary:mcp", + "operations": [ + { "tool": "list_calendar_events" }, + { "tool": "modify_calendar_event" } + ] + }, + "r3_conditional": { + "vocabulary": "urn:aauth:vocabulary:mcp", + "operations": [ + { "tool": "create_calendar_event" } + ] + }, + "iat": 1741824000, + "exp": 1741824900 +} +``` + +**`r3_uri`** and **`r3_s256`** provide audit provenance: a permanent, verifiable record of which R3 document was in effect at approval time. The AS can verify the R3 document by fetching `r3_uri` and checking `r3_s256`. + +**`r3_granted`** and **`r3_conditional`** use the same vocabulary-specific operation format as the R3 document's `operations` field and the agent's `r3_operations` request: + +- **`vocabulary`** (REQUIRED). The vocabulary URI. +- **`operations`** (REQUIRED). An array of operations using the vocabulary-specific structure. The AS MAY narrow the grant to fewer operations than defined in the R3 document. + +The distinction: `r3_granted` operations are fully authorized and the resource serves them. `r3_conditional` operations require the resource to challenge when the agent actually calls. On the challenge the resource builds a **per-call proposal** ({{per-call-proposals}}) — a content-addressed document carrying the specific parameters of the call — and the AS evaluates those concrete parameters before issuing a per-call auth token. + +## Resource Enforcement + +The resource matches each incoming API call against the auth token claims: + +1. **Match in `r3_granted`**: serve the request. +2. **Match in `r3_conditional`**: build a per-call proposal ({{per-call-proposals}}) and return `AAuth-Requirement` with a resource token referencing it. The AS evaluates the specific call against the proposed parameters. +3. **No match**: reject the request. + +No token introspection or R3 document fetch is needed at enforcement time. The resource uses the vocabulary it already understands. + +When `r3_operations` was not used (the agent received the resource token via a 401 rather than the authorization endpoint), the AS populates `r3_granted` and `r3_conditional` based on the operations defined in the R3 document and its own policy. The AS decides which operations to grant outright and which to make conditional. + +# Per-Call Proposals {#per-call-proposals} + +An `r3_conditional` operation is authorized in principle but not for any specific call: the consequences depend on the concrete parameters the agent supplies (who the email is addressed to, how large the payment is, which record is deleted). When the agent invokes such an operation, the resource challenges the call and the AS re-evaluates it against those parameters before issuing a per-call auth token. + +A **per-call proposal** is the document that carries the specifics of that one pending call. It is an R3 document scoped to a single invocation: same structure, same content-addressing ({{content-addressing}}), and the same AS/PS-only fetch restriction as a class R3 document. Reusing content-addressing keeps tokens small — they carry only the `r3_uri`/`r3_s256` reference, never the parameters — and binds the eventual approval to the exact call that was proposed. + +## Proposal Document + +In addition to the R3 document fields ({{r3-document}}), a per-call proposal carries: + +**`operations`** (REQUIRED). The single conditional operation being invoked, in the resource's vocabulary. + +**`parameters`** (REQUIRED). The concrete parameters of the call, machine-readable, for the AS to evaluate and the resource to bind. A large or sensitive value MAY be represented by a digest object in place of the inline value: + +- `s256` (REQUIRED). `BASE64URL(SHA-256(value-bytes))` of the parameter value as it will be presented at call time. +- `excerpt` (OPTIONAL). A short, human-readable excerpt of the value for display. +- `media_type` (OPTIONAL). The media type of the value. + +**`display`** (RECOMMENDED). Per-call human context for the user's approval decision. In addition to the structured fields defined in {{r3-document}}, a proposal's `display` MAY include a `detail` Markdown string. The following sections are RECOMMENDED as a convention (not normative requirements): `## Action`, `## To` / `## Recipient`, `## Details`, `## Content` (excerpt), `## Irreversible`. + +```json +{ + "vocabulary": "urn:aauth:vocabulary:mcp", + "operations": [ { "tool": "send_email" } ], + "parameters": { + "to": "mom@example.com", + "subject": "Dinner Sunday?", + "body": { "s256": "aBcD…", "excerpt": "Hi Mom, are you free…", "media_type": "text/plain" } + }, + "display": { + "summary": "Send an email as you", + "detail": "## Action\nSend an email\n\n## To\nmom@example.com\n\n## Details\nSubject: Dinner Sunday?\n\n## Content\nHi Mom, are you free…" + } +} +``` + +## Flow + +1. **Conditional challenge.** The agent invokes an `r3_conditional` operation. The resource builds the proposal, persists it keyed by its `r3_s256`, and returns `AAuth-Requirement` with a resource token whose `r3_uri`/`r3_s256` reference the proposal. The token carries only the reference, not the parameters. +2. **Approval.** The AS fetches the proposal and evaluates `parameters` per policy; the PS renders `display` for user consent. On approval, the AS issues a per-call auth token that echoes the proposal's `r3_uri`/`r3_s256` and lists the now-approved operation in `r3_granted`. +3. **Enforced retry.** The agent retries the actual call with the per-call auth token. The resource recovers the proposal from its store via `r3_s256` and MUST verify that the agent's actual parameters match the approved proposal. For any parameter represented as a digest, the resource MUST verify that `BASE64URL(SHA-256(presented-value))` equals the stored `s256`. If anything differs, the resource MUST reject the call. An approval to email one recipient cannot be replayed against another. + +## Large and Sensitive Payloads + +Representing a parameter as a digest keeps large or sensitive payloads out of every token and away from the PS: only the `s256` and a short `excerpt` appear in the proposal. The full bytes travel directly from the agent to the resource at call time, where the resource verifies them against the digest. This is also a privacy control — the resource chooses what the PS (and through it, the user-facing surface) sees versus what stays between the agent and the resource. + +Whether the AS or PS additionally *machine-evaluates* `parameters` (for example, auto-denying a payment over a threshold) or treats the proposal as display-for-human-consent only is deployment policy. The parameters are present in the proposal either way, so machine policy can be layered on without a format change. + +# Security Considerations + +## R3 Document Access Restriction + +The AS MUST authenticate itself when fetching `r3_uri` using an HTTP Message Signature as defined in the AAuth Protocol ([@!I-D.hardt-aauth-protocol]). The resource MUST reject requests not signed by its AS. + +This prevents agents from fetching R3 documents by following the `r3_uri` they carry in the resource token. Since a resource has exactly one AS, the resource only needs to recognize signatures from that AS. The agent opacity property (agents carry the hash of a document they cannot read) depends on this restriction. + +## R3 Endpoint Access Control + +The agent opacity property — agents carry the hash of a document they cannot read — depends entirely on the resource correctly restricting access to R3 document endpoints. If the resource fails to require a valid HTTP Message Signature from its AS on R3 document requests, or accepts signatures from keys other than its AS's, agents can fetch and read R3 documents, breaking the opacity guarantee. Implementations MUST treat R3 endpoint access control as a critical security requirement and SHOULD verify this restriction during deployment testing. + +## Hash Verification + +The AS MUST verify `r3_s256` against the fetched document before using it. Failure to verify allows a resource to serve different content than what was hashed in the resource token. + +## Audit Log Integrity + +The AS MUST write audit log entries atomically with token issuance. An auth token issued without a corresponding audit log entry creates an undetectable gap in the observability record. Implementations SHOULD use transactional writes or equivalent mechanisms. + +## Operation Validation + +For all vocabularies, the resource MUST validate declared operations against its authoritative definition (MCP tool list, OpenAPI spec, `.proto` file, GraphQL schema, AsyncAPI spec, WSDL document, or OData `$metadata`) before issuing a resource token. + +## Grant Enforcement + +Resources MUST enforce `r3_granted` and `r3_conditional` claims in auth tokens. Operations in `r3_granted` define fully authorized access. Operations in `r3_conditional` MUST trigger an `AAuth-Requirement` before being served. The resource MUST reject API calls that do not match an operation in either claim. + +# IANA Considerations + +## JWT Claims Registration + +This document requests registration of the following JWT claims in the IANA JSON Web Token Claims registry: + +| Claim | Description | Reference | +|-------|-------------|-----------| +| `r3_uri` | R3 document URI | This document | +| `r3_s256` | R3 document SHA-256 hash | This document | +| `r3_granted` | Fully authorized operations in vocabulary format | This document | +| `r3_conditional` | Conditionally authorized operations requiring per-call approval | This document | + +## R3 Vocabulary Registry + +This specification establishes the AAuth R3 Vocabulary Registry. The initial contents are: + +| Vocabulary URI | Interface Type | Reference | +|----------------|---------------|-----------| +| `urn:aauth:vocabulary:mcp` | MCP server | This document | +| `urn:aauth:vocabulary:openapi` | HTTP/REST | This document | +| `urn:aauth:vocabulary:grpc` | gRPC | This document | +| `urn:aauth:vocabulary:graphql` | GraphQL | This document | +| `urn:aauth:vocabulary:asyncapi` | Event-driven | This document | +| `urn:aauth:vocabulary:wsdl` | SOAP/WSDL | This document | +| `urn:aauth:vocabulary:odata` | OData | This document | + +New values may be registered following the Specification Required policy ([@!RFC8126]). + +# Implementation Status + +*Note: This section is to be removed before publishing as an RFC.* + +This section records the status of known implementations of the protocol defined by this specification at the time of posting of this Internet-Draft, and is based on a proposal described in [@RFC7942]. The description of implementations in this section is intended to assist the IETF in its decision processes in progressing drafts to RFCs. + +There are currently no known implementations. + +# Document History + +*Note: This section is to be removed before publishing as an RFC.* + +- draft-hardt-aauth-r3-00 + - Initial submission + +# Acknowledgments + +The author would like to thank reviewers for their feedback. + +{backmatter} + +# Design Rationale + +## Why Not RAR + +OAuth 2.0 Rich Authorization Requests ([@RFC9396]) defines `authorization_details` as a structured extension to authorization requests. RAR is a natural reference point for this work. R3 deliberately does not use or profile RAR for the following reasons: + +**Directionality.** RAR is client-declared: the agent constructs `authorization_details` and sends it to the AS. The agent defines what it wants. R3 is resource-declared: the resource defines what access it provides and signs that definition. The agent cannot modify or reframe it. This is the opposite directionality, and the security properties depend on it. + +**Agent opacity.** In R3, the R3 document is fetched by the AS directly from the resource, restricted to AS-only access. The agent carries a hash of a document it cannot read. RAR has no equivalent because the client constructs the authorization details and necessarily knows their content. + +**Content addressing.** R3 uses a content-addressed URI plus SHA-256 hash to pin the exact authorization semantics in effect at approval time. An auth token carrying `r3_uri` and `r3_s256` is a permanent, verifiable record. RAR carries no equivalent versioning or integrity guarantee. + +**Audit trail.** The AS records `r3_uri` in its audit log, creating a durable reference to the exact R3 document version. This is not possible with RAR's inline `authorization_details` structure. + +RAR and R3 are complementary. RAR remains appropriate for client-declared authorization detail. R3 addresses the resource-declared case that RAR was not designed for. + +# Vocabulary Summary + +| Vocabulary URI | Interface Type | Operation Identifier | Discovery Mechanism | +|----------------|---------------|---------------------|---------------------| +| `urn:aauth:vocabulary:mcp` | MCP server | Tool name | MCP tool discovery | +| `urn:aauth:vocabulary:openapi` | HTTP/REST | `operationId` | OpenAPI spec URL | +| `urn:aauth:vocabulary:grpc` | gRPC | `package.Service/Method` | Server reflection or `.proto` URL | +| `urn:aauth:vocabulary:graphql` | GraphQL | Operation name | GraphQL introspection | +| `urn:aauth:vocabulary:asyncapi` | Event-driven | `operationId` | AsyncAPI spec URL | +| `urn:aauth:vocabulary:wsdl` | SOAP/WSDL | Operation name | WSDL document URL | +| `urn:aauth:vocabulary:odata` | OData | Entity set or bound operation | `$metadata` URL | + +# Comparison with RAR + +| Property | RAR ([@RFC9396]) | R3 | +|----------|---------------|----| +| Who declares | Client (agent) | Resource | +| Direction | Client -> AS | Resource -> AS (via agent carrier) | +| Agent visibility | Agent constructs the detail | Agent carries opaque token | +| Versioning | None | Content-addressed URI + hash | +| Audit trail | Inline in request | `r3_uri` recorded by AS | +| Human display | Not specified | `display` section in R3 document | +| Irreversibility signal | Not specified | `display.irreversible` field | diff --git a/aauth-spec/v09/draft-hardt-httpbis-signature-key-06.txt b/aauth-spec/v09/draft-hardt-httpbis-signature-key-06.txt new file mode 100644 index 0000000..028f8f2 --- /dev/null +++ b/aauth-spec/v09/draft-hardt-httpbis-signature-key-06.txt @@ -0,0 +1,2184 @@ + + + + +HTTP D. Hardt +Internet-Draft Hellō +Intended status: Standards Track T. Meunier +Expires: 3 January 2027 Cloudflare + 2 July 2026 + + + HTTP Signature Keys + draft-hardt-httpbis-signature-key-06 + +Abstract + + This document defines two HTTP header fields and one Accept-Signature + parameter for use with HTTP Message Signatures as defined in RFC + 9421. The Signature-Key request header distributes public keys used + to verify signatures, with six initial key distribution schemes: + pseudonymous inline keys (hwk), self-issued key delegation via JWK + Thumbprint JWTs (jkt-jwt), identified signers with JWKS URI discovery + (jwks_uri), JWT-based delegation (jwt), self-issued JWTs (self-jwt), + and X.509 certificate chains (x509). The sigkey parameter extends + Accept-Signature (RFC 9421 Section 5) to indicate the type of + Signature-Key the server requires. The Signature-Error response + header provides structured error information when signature + verification fails. Together, these mechanisms enable flexible trust + models ranging from privacy-preserving pseudonymous verification to + horizontally-scalable delegated authentication and PKI-based identity + chains. + +Discussion Venues + + _Note: This section is to be removed before publishing as an RFC._ + + Source for this draft and an issue tracker can be found at + https://github.com/dickhardt/signature-key + (https://github.com/dickhardt/signature-key). + +Status of This Memo + + This Internet-Draft is submitted in full conformance with the + provisions of BCP 78 and BCP 79. + + Internet-Drafts are working documents of the Internet Engineering + Task Force (IETF). Note that other groups may also distribute + working documents as Internet-Drafts. The list of current Internet- + Drafts is at https://datatracker.ietf.org/drafts/current/. + + + + + + +Hardt & Meunier Expires 3 January 2027 [Page 1] + +Internet-Draft Signature-Keys July 2026 + + + Internet-Drafts are draft documents valid for a maximum of six months + and may be updated, replaced, or obsoleted by other documents at any + time. It is inappropriate to use Internet-Drafts as reference + material or to cite them other than as "work in progress." + + This Internet-Draft will expire on 3 January 2027. + +Copyright Notice + + Copyright (c) 2026 IETF Trust and the persons identified as the + document authors. All rights reserved. + + This document is subject to BCP 78 and the IETF Trust's Legal + Provisions Relating to IETF Documents (https://trustee.ietf.org/ + license-info) in effect on the date of publication of this document. + Please review these documents carefully, as they describe your rights + and restrictions with respect to this document. Code Components + extracted from this document must include Revised BSD License text as + described in Section 4.e of the Trust Legal Provisions and are + provided without warranty as described in the Revised BSD License. + +Table of Contents + + 1. Conventions and Definitions . . . . . . . . . . . . . . . . . 3 + 2. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 4 + 3. Signature-Key HTTP Request Header . . . . . . . . . . . . . . 5 + 3.1. Label Consistency . . . . . . . . . . . . . . . . . . . . 6 + 3.2. Multiple Signatures . . . . . . . . . . . . . . . . . . . 6 + 3.3. Header Web Key (hwk) . . . . . . . . . . . . . . . . . . 6 + 3.4. JKT JWT Self-Issued Key Delegation (jkt-jwt) . . . . . . 8 + 3.5. JWKS URI Discovery (jwks_uri) . . . . . . . . . . . . . . 11 + 3.6. JWT Confirmation Key (jwt) . . . . . . . . . . . . . . . 12 + 3.7. Self-Issued JWT (self-jwt) . . . . . . . . . . . . . . . 14 + 3.8. X.509 Certificates (x509) . . . . . . . . . . . . . . . . 16 + 4. Accept-Signature sigkey Parameter . . . . . . . . . . . . . . 17 + 4.1. Parameter Definition . . . . . . . . . . . . . . . . . . 17 + 4.2. Label Binding . . . . . . . . . . . . . . . . . . . . . . 18 + 4.3. Response Status Codes . . . . . . . . . . . . . . . . . . 19 + 4.4. sigkey Semantics . . . . . . . . . . . . . . . . . . . . 19 + 4.4.1. jkt . . . . . . . . . . . . . . . . . . . . . . . . . 19 + 4.4.2. uri . . . . . . . . . . . . . . . . . . . . . . . . . 20 + 4.4.3. x509 . . . . . . . . . . . . . . . . . . . . . . . . 20 + 4.5. Incremental Adoption . . . . . . . . . . . . . . . . . . 20 + 4.6. Coexistence with WWW-Authenticate . . . . . . . . . . . . 21 + 4.7. Examples . . . . . . . . . . . . . . . . . . . . . . . . 21 + 4.8. Client Processing . . . . . . . . . . . . . . . . . . . . 22 + 5. Signature-Error HTTP Response Header . . . . . . . . . . . . 22 + 5.1. Header Structure . . . . . . . . . . . . . . . . . . . . 22 + + + +Hardt & Meunier Expires 3 January 2027 [Page 2] + +Internet-Draft Signature-Keys July 2026 + + + 5.2. Response Body . . . . . . . . . . . . . . . . . . . . . . 23 + 5.3. Access Denied . . . . . . . . . . . . . . . . . . . . . . 23 + 5.4. Error Codes . . . . . . . . . . . . . . . . . . . . . . . 23 + 5.4.1. unsupported_algorithm . . . . . . . . . . . . . . . . 23 + 5.4.2. invalid_signature . . . . . . . . . . . . . . . . . . 24 + 5.4.3. invalid_input . . . . . . . . . . . . . . . . . . . . 24 + 5.4.4. invalid_request . . . . . . . . . . . . . . . . . . . 24 + 5.4.5. invalid_key . . . . . . . . . . . . . . . . . . . . . 24 + 5.4.6. unknown_key . . . . . . . . . . . . . . . . . . . . . 25 + 5.4.7. invalid_jwt . . . . . . . . . . . . . . . . . . . . . 25 + 5.4.8. expired_jwt . . . . . . . . . . . . . . . . . . . . . 25 + 6. Security Considerations . . . . . . . . . . . . . . . . . . . 25 + 6.1. Key Validation . . . . . . . . . . . . . . . . . . . . . 25 + 6.2. Caching and Performance . . . . . . . . . . . . . . . . . 26 + 6.3. Scheme-Specific Risks . . . . . . . . . . . . . . . . . . 26 + 6.4. Algorithm Selection . . . . . . . . . . . . . . . . . . . 28 + 6.5. Signature-Key Integrity . . . . . . . . . . . . . . . . . 28 + 7. Privacy Considerations . . . . . . . . . . . . . . . . . . . 28 + 7.1. Pseudonymity vs. Identity . . . . . . . . . . . . . . . . 29 + 7.2. Key Discovery Tracking . . . . . . . . . . . . . . . . . 29 + 7.3. JWT Contents . . . . . . . . . . . . . . . . . . . . . . 29 + 8. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 30 + 8.1. HTTP Field Name Registration . . . . . . . . . . . . . . 30 + 8.2. Signature-Key Scheme Registry . . . . . . . . . . . . . . 30 + 8.2.1. Registration Procedure . . . . . . . . . . . . . . . 30 + 8.2.2. Initial Registry Contents . . . . . . . . . . . . . . 31 + 8.2.3. Registration Template . . . . . . . . . . . . . . . . 31 + 8.3. HTTP Signature Metadata Parameters . . . . . . . . . . . 31 + 8.4. URN Sub-namespace Registration . . . . . . . . . . . . . 32 + 8.5. Signature Error Code Registry . . . . . . . . . . . . . . 32 + 8.5.1. Initial Registry Contents . . . . . . . . . . . . . . 32 + 9. Document History . . . . . . . . . . . . . . . . . . . . . . 33 + 10. Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . 34 + 11. References . . . . . . . . . . . . . . . . . . . . . . . . . 34 + 11.1. Normative References . . . . . . . . . . . . . . . . . . 34 + 11.2. Informative References . . . . . . . . . . . . . . . . . 36 + Appendix A. Design Rationale . . . . . . . . . . . . . . . . . . 36 + A.1. Why jwks_uri Instead of Inline JWKS? . . . . . . . . . . 36 + A.2. Why a Separate Header? . . . . . . . . . . . . . . . . . 37 + A.3. Why Schemes Instead of Just a Key and Key ID? . . . . . . 38 + A.4. Why Strings Instead of Byte Sequences for hwk? . . . . . 38 + Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . 38 + +1. Conventions and Definitions + + {::boilerplate bcp14-tagged} + + + + + +Hardt & Meunier Expires 3 January 2027 [Page 3] + +Internet-Draft Signature-Keys July 2026 + + +2. Introduction + + HTTP Message Signatures [RFC9421] provides a powerful mechanism for + creating and verifying digital signatures over HTTP messages. To + verify a signature, the verifier needs the signer's public key. + While RFC 9421 defines signature creation and verification + procedures, it intentionally leaves key distribution to application + protocols, recognizing that different deployments have different + trust requirements. + + This document defines: + + * *Signature-Key* (Signature-Key HTTP Request Header (#signature- + key-http-request-header)) — a request header that distributes + public keys for HTTP Message Signature verification. The header + supports six schemes, each designed for different trust models and + operational requirements: + + 1. *Header Web Key (hwk)* - Self-contained public keys for + pseudonymous verification + 2. *JKT JWT (jkt-jwt)* - Self-issued key delegation via JWK + Thumbprint JWTs ("jacket jot") + 3. *JWKS URI (jwks_uri)* - Identified signers with key discovery + via metadata + 4. *JWT (jwt)* - Delegated keys embedded in signed JWTs for + horizontal scale + 5. *Self-Issued JWT (self-jwt)* - Self-signed JWTs where the + signer and issuer are the same party + 6. *X.509 (x509)* - Certificate-based verification with PKI trust + chains + + Additional schemes may be defined through the IANA registry + established by this document. + + * *sigkey* (Accept-Signature sigkey Parameter (#accept-signature- + sigkey-parameter)) — a parameter for the Accept-Signature header + ([RFC9421], Section 5) that indicates the type of Signature-Key + the server requires. This extends RFC 9421's existing mechanism + for requesting signatures rather than defining a new header. + + * *Signature-Error* (Signature-Error HTTP Response Header + (#signature-error-http-response-header)) — a response header that + provides structured error information when signature verification + fails, enabling clients to diagnose and correct signing issues. + + The Signature-Key header works in conjunction with the Signature- + Input and Signature headers defined in RFC 9421, using matching + labels to correlate signature metadata with keying material. + + + +Hardt & Meunier Expires 3 January 2027 [Page 4] + +Internet-Draft Signature-Keys July 2026 + + +3. Signature-Key HTTP Request Header + + The Signature-Key header provides the public key or key reference + needed to verify an HTTP Message Signature. It is a Structured Field + Dictionary [RFC8941] keyed by signature label, where each member + describes how to obtain the verification key for the corresponding + signature. + + *Format:* + + Signature-Key: