PermDock
Standards

OAuth for agent delegation

How RFC 9396 Rich Authorization Requests, RFC 8693 token exchange, DPoP, CIBA and the IETF agent-delegation drafts shape PermDock's two-principal subject and the rule that an agent can never exceed its user.

Draft posture: build (RFC 8693, RFC 9396, RFC 9449; remaining IETF agent-delegation drafts tracked on the watch list)

PermDock implements the two-principal subject (principal, actor, delegation), matches RAR authorization_details against permissions, records RFC 8693 act chains, and derives actor from Web Bot Auth signatures.

What it is

A cluster of OAuth specifications and drafts describes how a human's authority is handed to software that acts for them:

  • RFC 9396 Rich Authorization Requests (RAR). Replaces flat scopes with an authorization_details array of typed JSON objects (type, actions, locations, datatypes, and type-specific fields). It is the carrier of constrained authority: not "may access posts" but "may update posts where the author is this user".
  • RFC 8693 token exchange. Lets a client trade one token for another, optionally with an actor_token, producing tokens that name both who the request is for and who is making it (act claim).
  • DPoP (RFC 9449). Binds a token to a key so a stolen token is useless without the private key; relevant when agents hold long-lived credentials.
  • CIBA. Client-Initiated Backchannel Authentication: the agent asks, the user approves on a separate device. A standards-based human-in-the-loop primitive.
  • Agent Delegation Chain (WIMSE draft): carries authority as RAR authorization_details across multiple agent hops and enforces offline-verifiable monotonic attenuation: each hop can only narrow what it received, and a verifier can check the whole chain without calling back to the issuer.
  • Credential Delegation Protocol (WIMSE draft): composes RFC 8693, DPoP, RAR and CIBA into one delegation flow.
  • OAuth for AI agents on behalf of a user: adds requested_actor to the authorization request and actor_token to the token request so the resulting token names the agent as well as the user.
  • MCP Enterprise-Managed Authorization. ID-JAG via RFC 8693 token exchange redeemed with RFC 7523; see MCP authorization.
  • Transaction tokens. Short-lived tokens that carry authorization context across microservices inside a trust domain.

Why it matters for PermDock

The net effect of these specifications is that an authorization check is not "can this user" but "can this user, acting through this agent, under this delegated authority". A single-subject API cannot express that, so PermDock's subject has two principals:

  • principal: the human or service whose grants are evaluated.
  • actor (optional): the agent making the call: an MCP clientId, an AI SDK agent name, a Web Bot Auth key, an A2A card.
  • delegation (optional): the authority the principal handed to the actor: OAuth scopes and/or RAR authorization_details, or an attenuated delegation chain.

A decision is principal grants intersected with delegated authority. The intersection is what makes "an agent can never exceed its user" a structural property rather than a policy the developer has to remember to write. See subject and delegation.

How PermDock uses it

import { createPermDock } from "permdock";

// Human case
const permdock = await createPermDock(policy, user);

// Agent case: actor + delegation
const asAgent = await createPermDock(policy, user, {
  actor: { id: authInfo.clientId, kind: "mcp-client" },
  delegation: {
    scopes: authInfo.scopes, // OAuth scopes
    authorizationDetails: authInfo.authorization_details, // RFC 9396 objects, if present
  },
});
asAgent.can(permissions.post.update, post); // true only if the user may AND the delegation covers it

Adapters fill actor and delegation automatically from authInfo (MCP), runtimeContext (AI SDK), the verified signature (Web Bot Auth) or the Agent Card (A2A), so application code rarely constructs them by hand.

Matching authorization_details to permissions

Each permission reference carries a scope (post:update), and RAR entries are matched on the permission's resource and action. An entry in delegation.authorizationDetails covers a permission when its type equals the resource name and its actions, when present, include the permission's action. An identifier, when present, narrows the entry to the one resource whose id it names; an empty actions array covers nothing. Other members (locations, datatypes, type-specific constraints) are the authorization server's to render and enforce; PermDock does not widen or narrow a grant from them, and the principal's own grant conditions still apply.

{
  "type": "post",
  "actions": ["update"],
  "locations": ["https://api.example.com/posts"]
}

Delegation chains

Chain verification belongs to the token layer. permdock/jwt checks that a nested RFC 8693 act claim is well formed, takes the outermost act.sub as the actor (RFC 8693 section 4.1: the current actor; nested act claims are prior actors and never decide access), and records the whole chain as delegation.chain for audit; a malformed chain yields the anonymous subject. Core trusts the scopes and authorization_details the verified token carries.

Calls with no human principal

A service agent acting on its own behalf is a principal with kind: 'service' and its own roles, not a separate subject shape. Between services inside one trust domain, an AuthZEN call carries the decision; PermDock defines no transaction token profile.

Attenuation invariants

  • A delegation may only remove or narrow; a grant that the principal lacks cannot be added by any authorization_details entry, actor_token or chain hop.
  • An empty scopes list is no delegated authority: every check is denied with reason no-delegation. An actor with no delegation at all is denied the same way; a subject with no actor is a human call and is not narrowed by delegation.

What PermDock does not do

PermDock does not issue tokens, run token exchange, validate DPoP proofs or drive CIBA. Those belong to the authorization server and the transport layer. PermDock consumes their output.

Mapping table

OAuth conceptPermDock concept
Resource owner / user in the tokenprincipal
client_id, act claim, actor_token, requested_actoractor
OAuth scopedelegation.scopes, matched against permission.scope
RFC 9396 authorization_detailsdelegation.authorizationDetails; type matched against the resource name, actions against the action
RAR constraint payloadEnforced by the authorization server; the grant's own condition still applies
Agent Delegation Chain hops, RFC 8693 actdelegation.chain, verified by the token layer and recorded for audit
Monotonic attenuationStructural: decision = principal grants ∩ delegation
RFC 8693 token exchange, ID-JAGTransparent; resulting token's claims feed principal, actor, delegation
DPoP, CIBATransport and consent layer; CIBA is one way approval-required can be fulfilled
Transaction tokensNot used; services call each other's AuthZEN endpoint
Service agent with no humanprincipal with kind: 'service'

Sources

Last updated on

On this page