AAuth Events — The AP as the Agent’s Inbox
Source:
draft-hardt-aauth-events-00. This is the one AAuth extension where the AP has a live protocol role beyond token issuance. Summary + the parts our implementation must get exactly right.
1. Problem & shape
Agents can’t receive webhooks (no public URL, intermittent execution). AAuth Events makes the AP the agent’s permanent event inbox — the Web-Push pattern, but the “push service” already has a cryptographic trust relationship with the agent.
Four phases:
- Subscribe-token acquisition (AP-internal, non-normative): agent asks its AP;
AP mints an
eid, stores a subscription record, returns anaa-subscribe+jwt. - Subscription registration (normative): agent presents the subscribe token as the
Signature-KeyJWT on a signed POST to the resource’s subscription endpoint (or a pre-authorized ticket URL for protected channels). - Delivery, resource → AP (normative): on event fire, resource POSTs to the AP’s
event_endpointpresenting anaa-event+jwtviaSignature-Key, body = payload. - Delivery, AP → agent (AP-internal, non-normative): poll / long-poll / SSE / push.
2. Subscribe token (typ: aa-subscribe+jwt) — we issue these
Signed by the AP’s key (same JWKS as agent tokens). Claims:
| claim | value |
|---|---|
iss |
AP URL (resource uses it to find our metadata → event_endpoint) |
dwk |
aauth-agent.json |
sub |
agent identifier |
aud |
the resource authorized to deliver events for this eid |
cnf.jwk |
agent’s current signing key (verifies the registration HTTP sig) |
eid |
opaque AP-generated correlation id, unique at the AP |
iat/exp |
registration validity window (NOT subscription lifetime) — default 24 h |
max_uses |
optional; AP-enforced cap on accepted event tokens for this eid; absent = unlimited |
Notes: the subscribe token replaces the agent token on the registration request —
structurally analogous (AP-signed + cnf.jwk), distinguished by typ. Resources SHOULD
reject duplicate registrations per eid.
3. Event token (typ: aa-event+jwt) — we verify these
Signed by the resource. Claims: iss (resource), dwk: aauth-resource.json,
aud (agent identifier, not the AP), eid, iat, exp (= the agent’s response
deadline; event-specific). No cnf — transport envelope only; event data rides in the
POST body (AsyncAPI-schema’d).
Signature-Key extension: when a jwt-scheme JWT has dwk but no cnf, the HTTP
signing key is the JWT’s own signing key — resolved from {iss}/.well-known/{dwk} by
header kid. One key verifies both the JWT and the HTTP signature.
4. AP validation of a delivery (POST /events)
The spec lists these checks (draft-events §Event Delivery). apd runs them in a
fail-cheap order that is a safe reordering — no check depends on another’s side
effects, and the “durably record before 202” MUST is preserved. The two
deliberate deviations from the spec’s numbering are noted inline.
- Extract JWT from
Signature-Key;typ == aa-event+jwt. - Validate event-token claims, including
expin the future (spec step 6, done early here — it’s a property of the token, cheap to reject on). - Resource JWKS via
{iss}/.well-known/aauth-resource.json(egress admission! resource URLs are attacker-influencable); verify JWT signature bykid(refresh once on a cache-hit key that fails, for silent re-keying). - Verify HTTP signature with the same key (dwk-without-cnf).
- Look up subscription by
eid→ else404. iss== the subscription’s authorized resource (the subscribe token’saud) → else403.aud== the subscription’s agent identifier → else403. Run before themax_usesincrement (reverse of the spec’s step 7/8) so a wrong-agent event never mutates the counter.max_uses: atomic use-count increment; exceeded →429.
Then: durably record the event before returning 202 (this is a MUST — a 202 is a
delivery promise). Response body: {"remaining_uses": N} when max_uses was set
(0 ⇒ subscription exhausted; we mark it complete after delivery), empty/{} otherwise.
Other errors: 400 malformed, 401 HTTP-sig failure.
5. AP → agent delivery (our design; spec leaves it open)
- Poll / long-poll (what
apdships):GET /inboxsigned with the agent token, optionally?wait=NorPrefer: wait=Nto long-poll (capped at 50 s). Returns and acks pending{event_token, payload}pairs for that agent. Durable until fetched. - SSE / push (not implemented; the events draft lists them as valid platform-dependent options): a persistent SSE/WebSocket stream, or mobile push (APNs/FCM), could deliver from the same inbox. Operators can bridge these from the poll inbox today.
Agent-side verification (their job, we document it): typ, resource JWKS signature,
aud == self, exp future, dedupe on (iss, eid), map eid → local context.
6. Security highlights
audscoping prevents a compromised resource from injecting into another resource’s channel — enforce check #5 strictly.- Replay: AP enforces
max_uses+exp; agent dedupes(iss, eid). - Ticket URLs (protected subscriptions) are the resource’s concern: short-lived,
single-use, bound to the subscribing agent’s
sub. - The AP sees every event envelope + payload → document retention (we keep events only until acked + a configurable TTL).
- Don’t cache the AP
event_endpointat the resource beyond normal HTTP caching of the well-known doc; the AP may move it. (We serve cacheable metadata with a modest max-age.)