Summary
AAuth authorizes an agent to perform operations. It has no ceremony for
linking a person to a third-party service that a resource brokers access to —
one that grants the agent nothing.
This issue proposes that shape for discussion. We intend to implement it first
and learn from it before proposing any draft text — filing it here so the
design is public and reviewable while we do.
The gap
Some resources are proxies: they front an upstream API (Google Calendar, HubSpot,
Slack) that the person must have connected before the resource can serve any
request at all. Today that connection can only be established as a side effect of
an authorization:
- The agent names operations at the authorization endpoint.
- The resource decides a connection is required and attaches an
interaction to
the resource token.
- The person server flows the person through it on the way to consent.
That works, but it means upstream access can never be acquired before it is
needed. Every new service, and every new upstream account, first surfaces as an
interruption in the middle of unrelated work. An agent that knows it will need
Gmail, Calendar and Contacts has no way to say so up front.
Proposal
1. A connection object in resource metadata
Its presence in /.well-known/aauth-resource.json declares that this resource
fronts an upstream the person must link, and describes what can be requested.
Notes on two choices that were not obvious:
- Raw upstream scopes, not an abstracted profile vocabulary. An earlier draft
named breadths (read / write / full). It lost information — an agent
understands .../auth/calendar.readonly better than it understands "read" —
and required per-resource mapping that scope-set comparison with the upstream's
own implication rules already does.
account_description present or absent is the whole account model. Present
means the agent must name an account when linking, because the identifier is
something the person knows and the upstream's authorize URL can pre-select
(Google's login_hint, Slack's team). Absent means it must not, because the
value is chosen inside the provider's UI and is not something a person can type
(a HubSpot portal id, a Threads profile). Everything else — how many accounts
exist, whether one is required on a given call — is observable at runtime and
does not belong in static metadata.
The description fields are read by the agent, not rendered to a person, so they
stay in one language. See §4.
2. A connections collection
POST {connection.endpoint} link an upstream account
GET {connection.endpoint} list this person's connections
DELETE {connection.endpoint}/{account} disconnect one
All authenticated with a person token.
POST body: { scopes?, account? }. Omitting scopes requests the declared
defaults; a requested set must be a subset of what is declared. account is
present exactly when account_description is.
The response is a resource token carrying an interaction and no scope
claim (§3). Already linked at those scopes or broader returns a status, not an
error — retry-safety is what makes "link everything up front" a safe instruction
for an agent.
GET returns, per connection: the account, a human label where the account key
is not itself human, the scopes held, when it was connected, and whether the
grant is still believed live. Two details that matter in practice: return the
implication-expanded scope set, so a client does not need the upstream's
hierarchy rules to reason correctly; and be explicit that this is what the
resource believes it holds, since a person can revoke at the upstream without
telling anyone.
3. The resource token needs no new claim
A link is signalled by the interaction the token already carries. Adding a claim
describing the connection was tried and removed: the person server does not
render it, its logic needs only to know that an interaction is required, it
never sends the value back to the resource, and the agent already knows what it
asked for. The resource holds the detail on the pending record the interaction
code keys.
That leaves one new invariant:
A scope claim is present exactly when an auth token will be issued.
A link ceremony issues nothing, so its token carries no scope. This conflicts
with scope being unconditionally REQUIRED on a resource token, which is the one
normative change the proposal needs.
The person server's handling becomes three independent tests:
r3 reference present → render consent from the document
else scope present → render consent from the scope descriptions
else → no consent card
interaction present → send the person to the resource's interaction endpoint
scope present → issue an auth token
else → terminate the ceremony without one
An authorization and a link compose — a token may carry both, which is
exactly the case that exists today when a resource decides a connection is needed
before it can honour an authorization.
4. Two audiences for text
Worth stating explicitly because it settled several questions above: text in this
protocol has two audiences, and they want opposite treatment.
- A person reads it — consent cards, interaction explainers. Must be an
identifier the renderer localises, or language-tagged.
- An agent reads it and renders to the person in their own language — tool
descriptions, operation summaries, scope descriptions. Plain, single-language
prose is correct; translating it is waste.
A corollary that removed a metadata field: naming what an upstream calls an
account (workspace, organisation, portal) looks like a localisation lever
and is really a localisation trap. A vendor's term of art does not survive
translation, and the authoritative word is whatever that vendor uses in that
locale — which a fixed enum cannot mirror. A generic noun that is right
everywhere beats a specific one that is wrong in most places.
5. Interaction endpoints belong in metadata
Not strictly part of this ceremony, but it came out of designing it and applies
to every interaction AAuth already has.
Today an interaction is carried as a URL plus a code. If the endpoint is
published in resource metadata and only the code travels, the redirect target
stops being a per-request value chosen by the token issuer and becomes a stable,
published property that a registry can validate once and a reconcile job can
watch for drift. It costs nothing to fetch: the resource token already names the
well-known document that publishes its signing key, so any party verifying the
token has that document in hand.
Status
Not a draft PR. We are building this to find out whether it is the right shape,
and would rather discover the problems in an implementation than in spec text.
Comments on the shape — particularly the scope-implies-issuance invariant in
§3, and whether the connections collection is the right home for this — are very
welcome.
Summary
AAuth authorizes an agent to perform operations. It has no ceremony for
linking a person to a third-party service that a resource brokers access to —
one that grants the agent nothing.
This issue proposes that shape for discussion. We intend to implement it first
and learn from it before proposing any draft text — filing it here so the
design is public and reviewable while we do.
The gap
Some resources are proxies: they front an upstream API (Google Calendar, HubSpot,
Slack) that the person must have connected before the resource can serve any
request at all. Today that connection can only be established as a side effect of
an authorization:
interactiontothe resource token.
That works, but it means upstream access can never be acquired before it is
needed. Every new service, and every new upstream account, first surfaces as an
interruption in the middle of unrelated work. An agent that knows it will need
Gmail, Calendar and Contacts has no way to say so up front.
Proposal
1. A
connectionobject in resource metadataIts presence in
/.well-known/aauth-resource.jsondeclares that this resourcefronts an upstream the person must link, and describes what can be requested.
Notes on two choices that were not obvious:
named breadths (
read/write/full). It lost information — an agentunderstands
.../auth/calendar.readonlybetter than it understands"read"—and required per-resource mapping that scope-set comparison with the upstream's
own implication rules already does.
account_descriptionpresent or absent is the whole account model. Presentmeans the agent must name an account when linking, because the identifier is
something the person knows and the upstream's authorize URL can pre-select
(Google's
login_hint, Slack'steam). Absent means it must not, because thevalue is chosen inside the provider's UI and is not something a person can type
(a HubSpot portal id, a Threads profile). Everything else — how many accounts
exist, whether one is required on a given call — is observable at runtime and
does not belong in static metadata.
The
descriptionfields are read by the agent, not rendered to a person, so theystay in one language. See §4.
2. A connections collection
All authenticated with a person token.
POSTbody:{ scopes?, account? }. Omittingscopesrequests the declareddefaults; a requested set must be a subset of what is declared.
accountispresent exactly when
account_descriptionis.The response is a resource token carrying an interaction and no
scopeclaim (§3). Already linked at those scopes or broader returns a status, not an
error — retry-safety is what makes "link everything up front" a safe instruction
for an agent.
GETreturns, per connection: the account, a human label where the account keyis not itself human, the scopes held, when it was connected, and whether the
grant is still believed live. Two details that matter in practice: return the
implication-expanded scope set, so a client does not need the upstream's
hierarchy rules to reason correctly; and be explicit that this is what the
resource believes it holds, since a person can revoke at the upstream without
telling anyone.
3. The resource token needs no new claim
A link is signalled by the interaction the token already carries. Adding a claim
describing the connection was tried and removed: the person server does not
render it, its logic needs only to know that an interaction is required, it
never sends the value back to the resource, and the agent already knows what it
asked for. The resource holds the detail on the pending record the interaction
code keys.
That leaves one new invariant:
A link ceremony issues nothing, so its token carries no
scope. This conflictswith
scopebeing unconditionally REQUIRED on a resource token, which is the onenormative change the proposal needs.
The person server's handling becomes three independent tests:
An authorization and a link compose — a token may carry both, which is
exactly the case that exists today when a resource decides a connection is needed
before it can honour an authorization.
4. Two audiences for text
Worth stating explicitly because it settled several questions above: text in this
protocol has two audiences, and they want opposite treatment.
identifier the renderer localises, or language-tagged.
descriptions, operation summaries, scope descriptions. Plain, single-language
prose is correct; translating it is waste.
A corollary that removed a metadata field: naming what an upstream calls an
account (
workspace,organisation,portal) looks like a localisation leverand is really a localisation trap. A vendor's term of art does not survive
translation, and the authoritative word is whatever that vendor uses in that
locale — which a fixed enum cannot mirror. A generic noun that is right
everywhere beats a specific one that is wrong in most places.
5. Interaction endpoints belong in metadata
Not strictly part of this ceremony, but it came out of designing it and applies
to every interaction AAuth already has.
Today an interaction is carried as a URL plus a code. If the endpoint is
published in resource metadata and only the code travels, the redirect target
stops being a per-request value chosen by the token issuer and becomes a stable,
published property that a registry can validate once and a reconcile job can
watch for drift. It costs nothing to fetch: the resource token already names the
well-known document that publishes its signing key, so any party verifying the
token has that document in hand.
Status
Not a draft PR. We are building this to find out whether it is the right shape,
and would rather discover the problems in an implementation than in spec text.
Comments on the shape — particularly the
scope-implies-issuance invariant in§3, and whether the connections collection is the right home for this — are very
welcome.