AAuth Protocol
A clean-slate design for agent identity and authorization. One of two unrelated proposals called AAuth. This one does not extend OAuth.
The agent has a key of its own and signs every request. A server the user chose tells the API who the agent acts for and what they approved.
In most OAuth deployments an agent is a client that someone registered: an identifier one authorization server issued and no other recognizes. That suits software whose APIs are known before it ships. An agent finds its APIs as it works, in organizations where nobody has registered it.
AAuth starts from the other end. It is not an OAuth extension. It is a separate protocol, written by the editor of the OAuth 2.0 specification. Each agent has its own key pair and an identifier that anyone can check, and it signs every request it makes. Every token in the protocol is bound to that key, so none of them is a bearer token. The user is represented by a person server, which they choose. It tells an API which person the agent acts for, asks that person when something needs their approval, and issues the token that records the answer.
AAuth is an individual draft in its eleventh revision, which is why this page is rated experimental. An unrelated OAuth draft, titled “AAuth - Agentic Authorization OAuth 2.1 Extension”, goes by the same short name. This page is not about that one.
202 from any endpoint as an instruction to poll.Steps 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 1313 steps between 4 parties. Each step shows the message and who holds which credential afterward. Use ←→ or select any arrow.
AAuth defines five arrangements, which it calls resource access modes. The diagram draws the one it calls PS authorization, PS for person server: an agent, an API with no access server of its own, and the user’s person server. It also shows the user being asked, which the person server decides each time. The five are compared below.
Drag to see every party and step. Tap a step to jump to its explanation.
Part 1 of 5The agent starts with a key of its own and a token that names it
The agent generates a key pair and proves itself to its agent provider: the service that stands behind it, such as a vendor’s backend or a company’s platform. A self-hosted agent is its own provider. How the agent proves itself depends on where it runs, and a companion draft covers that.
The provider returns an agent token. It binds the public key to an identifier of the form aauth:name@domain, and the draft recommends it last no more than a day. ps names the person server of the user this agent works for.
Anyone can verify the token by fetching the provider’s keys from a well-known address on its domain. Nobody has to register the agent first.
{
"iss": "https://agents.example",
"dwk": "aauth-agent.json",
"sub": "aauth:assistant.7f3c@agents.example",
"ps": "https://ps.example",
"cnf": {
"jwk": {
"kty": "OKP",
"crv": "Ed25519",
"alg": "Ed25519",
"x": "ea1QQGf1oeC9vnp0g3rNa9DgNGmjBkODCdFgOaolCss"
}
},
"jti": "at-51c2e0",
"iat": 1794240000,
"exp": 1794326400
}Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
Part 2 of 5The person server gives the agent a token that names its person
The calendar API’s metadata declares access_mode as auth-token, which means the first call has to carry a person token. That declaration is optional. An API that leaves it out says the same at the first call, with 401 and AAuth-Requirement: requirement=person-token. So the agent asks its person server for a person token, naming the API.
The rest of the metadata is not optional for this API. It names the API as an issuer and says where its keys are, and the person server needs both to verify the resource token of step 5.
The request is signed. Signature-Key carries the agent token. Signature-Input lists what the signature covers, with the time it was made. Signature is the signature. Every request in this flow has all three; the later listings show only Signature-Key, until the last call.
POST /person HTTP/1.1
Host: ps.example
Content-Type: application/json
Content-Digest: sha-256=:3B5kr1N+7RsZx0N/Ye5sOXIa+xc6skzUiG/ieCr66oI=:
Signature-Key: sig=jwt;jwt="eyJhbGciOiJFZDI1NTE5IiwidHlwIjoiYWEtYWdlbnQrand0…"
Signature-Input: sig=("@method" "@authority" "@path" "signature-key"
"content-digest" "content-type");created=1794268800
Signature: sig=:hELT5RcN9P3baUmWh6xAAh6d+Ui+bGFKorz+GKROlOVu/ez0WmWEAjC0ISJtB8wCL1zzwLGg8ZK/+mmwtc7UAQ==:
{"resource":"https://calendar.example","capabilities":["interaction"]}Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
Part 2 of 5The person server gives the agent a token that names its person
The person server checks the agent token against the provider’s published keys, checks the signature with the key in it, and looks up which person this agent is bound to. Each agent is bound to exactly one. Alice connected this agent earlier and has let it act for her at the calendar API before, so the answer comes at once. For an API she has not used, the person server should ask her first, in the way steps 7 to 9 show.
A person token says who, and nothing about what. It has no scope. aud is one API. sub is the identifier the person server uses for Alice at that API, and the draft recommends a different one for each. cnf holds the agent’s key. It lasts an hour at most, and it does not name the agent.
{
"iss": "https://ps.example",
"dwk": "aauth-person.json",
"aud": "https://calendar.example",
"sub": "bc83dde689646e8541a62df13b879317",
"cnf": { "jwk": { "kty": "OKP", "crv": "Ed25519", "alg": "Ed25519",
"x": "ea1QQGf1oeC9vnp0g3rNa9DgNGmjBkODCdFgOaolCss" } },
"jti": "pt-3ab910",
"iat": 1794268800,
"exp": 1794272400
}Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
Part 3 of 5The API learns who the agent acts for, and writes down what it needs
Signed with the same key, and presenting the person token in place of the agent token. This is the first the API has heard of this agent. Nothing was registered beforehand, and nothing in the request says which agent it is.
AAuth-Capabilities tells the API what the agent can handle, so the API knows what it may ask of it.
GET /events HTTP/1.1
Host: calendar.example
AAuth-Capabilities: interaction
Signature-Key: sig=jwt;jwt="eyJhbGciOiJFZDI1NTE5IiwidHlwIjoiYWEtcGVyc29uK2p3dCIs…"Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
Part 3 of 5The API learns who the agent acts for, and writes down what it needs
The API verifies the person token: it is the right type, signed by keys its issuer publishes, issued for this API, and bound to the key that signed the request. It now knows which person this is about. Reading her calendar takes her consent, so it answers 401, with a resource token in the AAuth-Requirement header.
A resource token is the API’s own signed statement of the request: which API, which person at which person server, which person token it was shown, which key, which scope. aud is the person server that issued the person token, because this API has no access server of its own. The agent carries the token and cannot alter it. The draft recommends it expire within five minutes.
{
"iss": "https://calendar.example",
"dwk": "aauth-resource.json",
"aud": "https://ps.example",
"ps": "https://ps.example",
"sub": "bc83dde689646e8541a62df13b879317",
"presented_jti": "pt-3ab910",
"agent_jkt": "cGXbwNDIqrkzguDVr5oBxAEAY2kEm2LccCsY3WJtRX8",
"scope": "calendar.read",
"jti": "rt-90c1f4",
"iat": 1794268802,
"exp": 1794269102
}Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
Part 4 of 5The person server asks the user, then issues a token
First the agent checks the resource token: that it came from the API it just called, and that it names the agent’s own key, its person server and the person token it presented. Then it sends both tokens to the person server, signed, and presenting its agent token again.
justification is the agent’s own account of why it wants the access. The person server will show it to Alice as that and no more. capabilities says the agent can get Alice to a page, and it is stated on each request. Without it, a person server with no channel of its own to her has to refuse. Prefer: wait says how long the agent will hold the connection open for an answer.
POST /token HTTP/1.1
Host: ps.example
Content-Type: application/json
Prefer: wait=45
Signature-Key: sig=jwt;jwt="eyJhbGciOiJFZDI1NTE5IiwidHlwIjoiYWEtYWdlbnQrand0…"
{
"resource_token": "eyJhbGciOiJFZDI1NTE5IiwidHlwIjoiYWEtcmVzb3VyY2Urand0…",
"presented_token": "eyJhbGciOiJFZDI1NTE5IiwidHlwIjoiYWEtcGVyc29uK2p3dCIs…",
"justification": "Find a time next week when Alice and Bob are both free",
"capabilities": ["interaction"]
}Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
Part 4 of 5The person server asks the user, then issues a token
The person server verifies the resource token against the API’s published keys and checks that everything matches: it is addressed to this person server, bound to the key that signed this request, and names the person token that came with it. The request is in order. Alice has not approved calendar access for this agent, so the answer is 202 Accepted, with a pending URL to poll and a page to send her to.
In AAuth a wait is an ordinary answer, not an error, and any endpoint may give one. The code ties Alice’s visit to this pending request. It is not a credential, and holding it approves nothing.
HTTP/1.1 202 Accepted
Location: /pending/f7a3b9c
Retry-After: 5
Cache-Control: no-store
AAuth-Requirement: requirement=interaction;
url="https://ps.example/interact"; code="A1B2-C3D4"
Content-Type: application/json
{"status":"pending"}Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
Part 4 of 5The person server asks the user, then issues a token
The agent passes on the URL and the code, over whatever channel it has to her. A link the agent relays is a link an attacker can imitate, so the page Alice lands on has to show the whole request, for her to recognize or refuse.
A person server that has its own channel to Alice, such as a notification on her phone, can use that and leave the agent out. The draft calls that the stronger arrangement.
To approve, open https://ps.example/interact?code=A1B2-C3D4Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
Part 4 of 5The person server asks the user, then issues a token
Alice signs in to her person server; how is between the two of them. The page shows content from two sources and has to keep them apart. From the API: its name, and its own description of the scope, “Read your calendar events”. From the agent: its justification, marked as the agent’s claim. Where the API has described the access, the person server may not decide on the agent’s words alone.
She is answering her person server, on its page. The agent does not draw that page, and the code it relayed approves nothing.
Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
Part 4 of 5The person server asks the user, then issues a token
A signed GET to the pending URL, repeated at the interval the person server asked for. The person server checks the agent’s identity on every poll, so the URL is no use to anyone else.
Nothing comes back through Alice’s browser. There is no authorization code to intercept, and so nothing like PKCE is needed to protect one.
GET /pending/f7a3b9c HTTP/1.1
Host: ps.example
Prefer: wait=45
Signature-Key: sig=jwt;jwt="eyJhbGciOiJFZDI1NTE5IiwidHlwIjoiYWEtYWdlbnQrand0…"Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
Part 4 of 5The person server asks the user, then issues a token
An auth token records what Alice approved: this person, this API, this scope, this key. It lasts an hour at most, and never longer than the agent token and the person token it was issued from. Like the person token, it names no agent.
The agent checks it before use: issued by the server the resource token was addressed to, for the API it means to call, bound to its own key, and for the same person.
{
"iss": "https://ps.example",
"dwk": "aauth-person.json",
"aud": "https://calendar.example",
"ps": "https://ps.example",
"sub": "bc83dde689646e8541a62df13b879317",
"scope": "calendar.read",
"cnf": { "jwk": { "kty": "OKP", "crv": "Ed25519", "alg": "Ed25519",
"x": "ea1QQGf1oeC9vnp0g3rNa9DgNGmjBkODCdFgOaolCss" } },
"jti": "au-7d20b5",
"iat": 1794268890,
"exp": 1794272400
}Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
Part 5 of 5The agent signs its request, and the API checks token and key
The request of step 4, now presenting the auth token. The signature covers the method, the host, the path and the Signature-Key header, and carries the time it was made. The API accepts it for about a minute. A copy of the whole request can repeat this one call within that minute, unless the API remembers what it has seen. The token lifted out of it is useless without the private key.
The query string and the body are not covered unless the API says they must be.
GET /events HTTP/1.1
Host: calendar.example
AAuth-Capabilities: interaction
Signature-Key: sig=jwt;jwt="eyJhbGciOiJFZDI1NTE5IiwidHlwIjoiYWEtYXV0aCtqd3QiLCJr…"
Signature-Input: sig=("@method" "@authority" "@path" "signature-key");
created=1794268891
Signature: sig=:Vm2IefOv9exvy+TCf9aiDSCHN0khqpfE2uvNprUZqnfJVWUYdyoc6JSfGA43tJfGoWJpFTzTCEI1iXQLgwhHBQ==:Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
Part 5 of 5The agent signs its request, and the API checks token and key
The API verifies the token first, because the key that checks the signature is inside it: it is an auth token and not a person token, and it is signed by keys its issuer publishes. It then verifies the request’s signature with the token’s key, and checks that the token was issued for this API. It matches the issuer and subject to its record of Alice, or makes one. Then it applies its own rules. The person server vouched for who she is and what she agreed to. What calendar.read allows is still the API’s decision.
The API now knows which person this is for, what she approved, and that the caller holds the key. It does not know which agent the caller is.
Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
An API chooses among these, and may use different ones for different endpoints. It says which it needs when it answers a request it will not yet serve. They differ in what the API ends up knowing, and in who established it.
| Arrangement | What the API ends up knowing | Who established it | What the agent presents |
|---|---|---|---|
| Agent identity | Which agent | The agent’s provider | Its agent token. Nothing else is involved: this is the arrangement that takes the place of an API key |
| Resource-managed | Which agent and which person | The API’s own sign-in or consent, which can be the OAuth it already runs | Its agent token, with a session token from the API that works only under the agent’s signature |
| Person identity | Which person | The person server | A person token. The API decides what a person it recognizes may do |
| PS authorization, as drawn | Which person, and the scope they consented to | The person server | A person token, then an auth token from the person server |
| Federated authorization | Which person, and what its own policy decided | An access server that evaluates policy for the API | A person token, then an auth token from the access server, which the person server obtains for it |
In agent identity and resource-managed access, the API sees the agent’s identifier and can make rules about it. In the other three it does not: no token the API reads names the agent. In federated authorization the agent token does reach the access server, which may weigh what it says about the agent.
typ tells them apart. An API that skips that check treats “this is Alice’s agent” as “Alice approved this.” The draft says to test for the mistake, because it fails open.No registration. An OAuth client is known where it was registered or, with a Client ID Metadata Document, where its URL is accepted. An AAuth agent can be checked by anyone who can fetch its provider’s keys.
No bearer tokens. In OAuth, binding a token to a key is an addition: DPoP or mutual TLS. Here every token is bound to the agent’s key and every request is signed.
No authorization code. Nothing of value returns through the browser. The agent collects the token by polling, on a signed request.
The user’s server, not the API’s. In OAuth the authorization server belongs with the API, and the user has an account there. In the arrangement drawn, consent is collected by a server the user chose, which the API may never have heard of.
Most of the draft is outside this diagram.
Missions. The agent proposes a description of a whole task, and the user approves it at the person server. After that, the person server judges each request the agent brings it under the mission against that description and against what came before, and keeps a log of those requests. It does not see every call. Once an auth token is issued, the agent calls the API directly until the token expires, and it is the API that enforces what the token grants. An agent can also report to the person server what it did. That report is optional.
Access servers. An API that wants a policy engine of its own names one in the resource token, and the person server obtains the auth token there.
Chains. An API that calls another API acts as an agent in its turn, and presents the token it received as evidence of whom it is acting for. The draft deliberately puts no delegation chain in any token, which is the opposite choice to token exchange.
Companion drafts cover how an agent enrolls with its provider, authorization described in an API’s own vocabulary, and event delivery.
Both documents the diagram depends on are individual drafts: the protocol at revision -11 and HTTP Signature Keys at -09, each dated September 2026. A call for the HTTP working group to adopt the second ended in September 2026. The datatracker records: “WG doesn’t have consensus to adopt at this time.” The protocol’s author says AAuth has been adopted as a working group at the OpenID Foundation, and that specification work will migrate there.
Read it, and prototype with it. For something further along: MCP authorization does without registration, DPoP binds a token to a key, and CIBA reaches a user who is somewhere else.
See the full tracker for status and links.
What the flow implements, shows in a step, or must support. These decide its maturity rating.
These are required by the arrangement the diagram draws: the protocol, the header that carries the key, the signature format, and the thumbprint that ties a resource token to the agent’s key. Every arrangement depends on the first three, so none of them would be rated differently.
A clean-slate design for agent identity and authorization. One of two unrelated proposals called AAuth. This one does not extend OAuth.
Would give signed-request schemes a common way to find the signer’s key. AAuth is built on it.
The building block under Web Bot Auth, the WIMSE HTTP signature profile, AAuth, and the card networks’ agent protocols.
How DPoP and related schemes refer to the key a token is bound to.
Related reading. Not what the flow implements, and not used in a step.
A token copied alone from a log or a trace is refused by an API that checks the binding.
One of two unrelated proposals called AAuth. This one extends OAuth.
An agent that browses the open web signs its requests, so a site can tell which operator it belongs to without trusting a user-agent string.
Bind a token to a key so that stealing the token is not enough to use it.
The agent asks the authorization server to go and find the user. Approval arrives as a push notification, not a redirect.
How an MCP client that has never seen a server before discovers where to sign in, identifies itself, and gets a token good for that server alone.