DID-backed OAuth Federation for A2A — Mentionable v0.1
Status: Draft v0.1; implemented by @mentionable/connector-kit and
vicoop-bridge.
Profile / Agent Card extension URI:
https://mentionable.dev/ns/oauth-federation/v0.1
Last updated: 2026-09-03
This document defines an OAuth 2.0 Token Exchange profile through which a Mentionable Connector turns a short-lived, DID-signed assertion about a platform principal into a bearer access token for one A2A agent. It is a profile of RFC 8693, RFC 7523, RFC 8414, and RFC 8707; it does not define a new OAuth grant.
The exact compact JWTs and form fields in
fixtures/oauth-federation/v0.1/
are the executable conformance vectors for this document. The string
constants in
packages/connector-kit/src/oauth-federation.ts
are the byte-level registry. If prose and a committed fixture disagree, that
is a specification defect and MUST be resolved before claiming conformance.
0. Conformance language
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, NOT RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in BCP 14 (RFC 2119 and RFC 8174) when, and only when, they appear in all capitals.
1. Scope and final v0.1 topology
v0.1 supports only direct delegation by a relay-verified Connector:
platform principal S
|
v
Connector C
assertion issuer = C
OAuth client = C
A2A actor = C
|
v
Authorization Server / STS ---- access token ----> A2A Resource Server
|
v
target agent
For every accepted exchange:
subject_token.iss == authenticated client_id == derived actor
principal == verified subject_token.sub
purpose == delegation
The Authorization Server (AS), Security Token Service (STS), A2A Resource Server, and target agent are separate logical roles. A deployment MAY co-locate them, but MUST preserve their trust boundaries.
The profile deliberately separates:
- the platform principal, identified by the subject assertion’s
sub; - the Connector, which verifies the native platform event, issues the subject assertion, authenticates as the OAuth client, and is retained as the actor;
- the STS, which authenticates the Connector, verifies the subject assertion, applies receiver-owned policy, and issues authority;
- the Resource Server, which authenticates every A2A operation and enforces resource, scope, task ownership, and live policy; and
- the target agent, whose canonical A2A endpoint URL is the OAuth resource.
Client authentication alone MUST NOT authorize a platform principal. DID signature verification alone MUST NOT authorize one either. Authorization requires an exact receiver-owned caller-policy match (§8).
This topology is the final result of the two v0.1 amendments on issue #618. Amendment 2 supersedes the earlier bare Connector self-assertion rule: task-only renewal uses a task-continuation assertion that preserves the originating platform principal and names one task.
2. Identifiers and canonicalization
2.1 Connector identity
The Connector issuer and OAuth client_id MUST be the same exact DID string.
The reference implementation uses did:web. v0.1 comparisons are exact
string comparisons; implementations MUST NOT case-fold, trim, redirect, or
otherwise rewrite a presented issuer, client ID, verification-method ID,
resource, subject, method, or task ID during an authorization decision.
2.2 Platform subject and method
sub identifies the platform principal in the canonical namespace of the
authentication method. For Slack workspace members the form is:
slack:<workspace-or-team-id>/<user-id>
For example, the fixtures use slack:T0123456/U0456789. The method is a
non-empty URI identifying how the Connector authenticated the subject; the
fixtures use:
urn:mentionable:auth:slack-workspace-member:v0.1
Neither string is globally authoritative by itself. The collision-safe authorization identity is the exact tuple:
(issuer DID, method URI, platform subject)
Receivers MUST key policy on all three components and MUST use an unambiguous tuple representation, such as structured columns or length-prefixed components. Delimiter concatenation without escaping is not collision-safe.
2.3 Agent aliases and canonical resource
An @agent@domain address, its acct: URI, and an advertised agent DID are
discovery aliases, not OAuth resources. A client resolves the alias to an
Agent Card and obtains the canonical resource from §3. The canonical resource
MUST be the agent’s advertised, version-independent A2A endpoint URL and MUST
be sent verbatim as the single RFC 8707 resource value.
A task ID, A2A context ID, credential ID, assertion jti, address, or DID
MUST NOT be treated as an authorization credential or substituted for the
canonical resource.
3. Discovery
3.1 Agent Card extension
An A2A server advertises this profile with an exact
a2a.capabilities.extensions[] entry:
{
"uri": "https://mentionable.dev/ns/oauth-federation/v0.1",
"description": "DID-backed OAuth 2.0 Token Exchange for exact Mentionable federated callers.",
"required": true,
"params": {
"authorization_server": "https://bridge.example/.well-known/oauth-authorization-server",
"resource": "https://bridge.example/agents/AGENT_ID"
}
}
The uri match MUST be exact. Scheme changes, case changes, a trailing slash,
or another version are non-matches. params.authorization_server and
params.resource are REQUIRED non-empty strings. The former is the URL of
the RFC 8414 metadata document itself; clients MUST NOT derive another
well-known path from it. The latter is the canonical resource from §2.3.
Before exchanging or delivering a token, a Connector MUST verify that the
extension resource exactly equals the A2A endpoint to which it will send the
request. A mismatch MUST fail closed.
3.2 Authorization Server metadata
The URL above MUST return an RFC 8414 JSON object. At minimum, a v0.1 server MUST advertise:
{
"issuer": "https://bridge.example",
"token_endpoint": "https://bridge.example/oauth/token",
"grant_types_supported": ["urn:ietf:params:oauth:grant-type:token-exchange"],
"token_endpoint_auth_methods_supported": ["private_key_jwt"],
"token_endpoint_auth_signing_alg_values_supported": ["EdDSA"],
"scopes_supported": [
"a2a:message.send",
"a2a:message.stream",
"a2a:task.read",
"a2a:task.cancel",
"a2a:task.resubscribe",
"a2a:task.push-config"
],
"subject_token_types_supported": ["urn:ietf:params:oauth:token-type:jwt"]
}
subject_token_types_supported is a profile metadata extension. A server MAY
advertise other grants, scopes, or authentication methods for unrelated
profiles and MAY publish additional profile-selection metadata. A client MUST
verify the token-exchange grant and MUST verify private_key_jwt, EdDSA,
the JWT subject-token type, and every scope it intends to request before the
exchange. The Agent Card extension remains the v0.1 profile-selection signal.
An absent grant_types_supported does not imply token-exchange support.
Production token endpoints MUST use HTTPS. Fetchers MUST apply normal SSRF defenses to both Agent Card and metadata retrieval, including blocking loopback, link-local, and private-network targets unless explicitly operating in a local-development mode.
4. JWT and DID verification rules
4.1 Common JOSE header
Every v0.1 assertion is a compact JWS using this protected-header shape:
{
"alg": "EdDSA",
"typ": "<one exact type from §5>",
"kid": "did:web:connector.example#oauth-key-2026-08"
}
The verifier MUST pin alg to EdDSA and MUST match typ to the
presentation slot before signature verification. Algorithm inference from
the key or acceptance of none is forbidden. The kid MUST be an absolute
fragment DID URL whose base is exactly iss (<iss>#<non-empty-fragment>),
and that exact value MUST occur in the issuer DID document’s
assertionMethod relationship. The complete kid MUST satisfy the URI
lexical rules in §5.1; in particular its fragment delimiter MUST occur
exactly once and raw whitespace, controls, backslashes, invalid percent
escapes, and unmatched or non-authority brackets are forbidden.
4.2 Common claims and time profile
All three assertion types MUST contain well-typed iss, sub, aud, iat,
exp, and jti claims.
| Claim | Requirement |
|---|---|
iss | Exact Connector DID. |
sub | Type-specific subject from §5. |
aud | The exact token-endpoint URL. A string or a one-element array containing only that string is accepted. |
iat | NumericDate at issuance. |
exp | NumericDate; exp - iat MUST be positive and MUST NOT exceed 600 seconds. |
jti | Non-empty, freshly generated replay identifier. |
nbf | OPTIONAL; when present, the receiver MUST enforce it. |
The default assertion lifetime is 300 seconds, the maximum is 600 seconds,
and receiver clock tolerance is 60 seconds. A receiver MUST reject assertions
that are expired beyond tolerance, issued in the future beyond tolerance, or
whose nbf is in the future beyond tolerance.
The STS MUST consume each (iss, jti) once across all instances and retain
the replay record through at least exp + 60 seconds. Subject and client
assertions in one request therefore require distinct fresh jti values.
Production replay storage MUST provide cross-instance atomic registration;
the reference cache adapter accepts either a synchronous boolean or an
asynchronous boolean result so database and Redis implementations can perform
that registration durably.
The connector-kit combined verifyTokenExchange entry point requires a
replay cache and fails fast when it is absent; it MUST NOT return ok: true
without registering both assertions. The lower-level single-assertion
verifier keeps cache-less operation only for isolated cryptographic component
testing, not for an STS authorization decision.
4.3 DID document and key authorization
An issuer signature is eligible only after the receiver has matched a local
trust/policy entry (§8.1). Only then may it resolve the issuer DID document.
The document id MUST equal iss. The exact kid MUST occur in
assertionMethod, either as a string/id-only reference to a top-level
verificationMethod or as a full verification method embedded directly in
the relationship. It MUST resolve to a verification method controlled by that
issuer and MUST expose an Ed25519 public JWK. Embedded and referenced methods
receive identical checks. The method type MUST be
exactly JsonWebKey, controller MUST equal iss, and publicKeyJwk MUST
have kty: "OKP", crv: "Ed25519", and a non-empty string x; it MUST NOT
contain private d material:
{
"@context": ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/jwk/v1"],
"id": "did:web:connector.oauth-fixtures.mentionable.dev",
"verificationMethod": [
{
"id": "did:web:connector.oauth-fixtures.mentionable.dev#oauth-fixture-2026-08",
"type": "JsonWebKey",
"controller": "did:web:connector.oauth-fixtures.mentionable.dev",
"publicKeyJwk": {
"crv": "Ed25519",
"x": "UGfideXk63XnzqYdisiTAZ64iAHklpZox4h4kECItXU",
"kty": "OKP"
}
}
],
"assertionMethod": ["did:web:connector.oauth-fixtures.mentionable.dev#oauth-fixture-2026-08"]
}
A key merely present in verificationMethod, or present under a different
verification relationship, is unauthorized. A verification method MUST NOT
combine publicKeyJwk and publicKeyMultibase. When one Ed25519 key also
serves PlatformIdentityCredential v0.2, publishers MUST use distinct method
IDs; the Slack reference Connector uses #<kid> for its Multikey and
#<kid>-oauth-jwk for the OAuth JWK.
Connectors MUST sign only with their active key. During rotation, issuers MAY
keep the prior public method in assertionMethod long enough for assertions
already issued within the maximum lifetime and clock tolerance. DID resolvers
SHOULD cache documents for no more than five minutes, honor shorter HTTP cache
directives, and retry resolution once on an unknown kid or signature failure
before final rejection. Removing a compromised method is an emergency
revocation signal; receivers MUST NOT keep accepting it indefinitely from a
stale cache.
5. Assertion profiles
The decoded JSON below uses the exact values in the committed fixtures. The fixture files contain the normative compact-JWT bytes and signatures.
5.1 Platform-subject assertion
This assertion is REQUIRED whenever any message scope is requested.
// protected header
{
"alg": "EdDSA",
"typ": "mentionable-subject-assertion+jwt",
"kid": "did:web:connector.oauth-fixtures.mentionable.dev#oauth-fixture-2026-08"
}
// payload
{
"mentionable_method": "urn:mentionable:auth:slack-workspace-member:v0.1",
"iss": "did:web:connector.oauth-fixtures.mentionable.dev",
"sub": "slack:T0123456/U0456789",
"aud": "https://sts.oauth-fixtures.mentionable.dev/oauth/token",
"iat": 1787097600,
"exp": 1787097900,
"jti": "b1a2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"
}
mentionable_method MUST be in the profile’s conservative RFC 3986 absolute
URI lexical subset: it has a valid scheme and non-empty scheme-specific part,
uses only RFC 3986 ASCII URI characters and valid %HH escapes, and contains
at most one fragment delimiter. Raw whitespace, controls, and backslashes are
forbidden. Brackets are valid only as a balanced IPv6 host in a URI authority,
and a URI using // MUST have a non-empty authority host. Both opaque URNs and
hierarchical absolute URLs are valid. An authority permits at most one raw @;
its userinfo and host MUST use their RFC 3986 component characters, and an
explicit port MUST contain one or more decimal digits whose numeric value is
between 0 and 65535 inclusive. A relative reference is not valid. sub MUST
NOT equal iss.
The assertion represents a fresh, Connector-verified platform event. A
Connector MUST NOT mint it merely because it previously observed the same
subject.
5.2 Task-continuation assertion
This assertion MAY be used only for an exchange whose requested scopes are all task scopes. It is the only task-only renewal subject in v0.1.
// protected header
{
"alg": "EdDSA",
"typ": "mentionable-task-continuation-assertion+jwt",
"kid": "did:web:connector.oauth-fixtures.mentionable.dev#oauth-fixture-2026-08"
}
// payload
{
"mentionable_task_id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
"iss": "did:web:connector.oauth-fixtures.mentionable.dev",
"sub": "slack:T0123456/U0456789",
"aud": "https://sts.oauth-fixtures.mentionable.dev/oauth/token",
"iat": 1787097600,
"exp": 1787097900,
"jti": "c2b3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e"
}
sub is the originating platform principal captured when the task was
created, not the Connector. mentionable_task_id MUST be a non-empty exact
task ID. The Connector MAY re-mint a continuation assertion at each renewal,
but MUST derive it from a persisted binding established by the fresh origin
event and MUST use a new jti.
A continuation assertion requesting a2a:message.send or
a2a:message.stream MUST be rejected with invalid_request. Bare Connector
self-assertions are not accepted.
5.3 Client assertion (private_key_jwt)
The client authenticates under RFC 7523 with:
// protected header
{
"alg": "EdDSA",
"typ": "mentionable-client-assertion+jwt",
"kid": "did:web:connector.oauth-fixtures.mentionable.dev#oauth-fixture-2026-08"
}
// payload
{
"iss": "did:web:connector.oauth-fixtures.mentionable.dev",
"sub": "did:web:connector.oauth-fixtures.mentionable.dev",
"aud": "https://sts.oauth-fixtures.mentionable.dev/oauth/token",
"iat": 1787097600,
"exp": 1787097900,
"jti": "e4d5f6a7-b8c9-4d0e-9f2a-3b4c5d6e7f8a"
}
iss, sub, and the form client_id MUST be identical. The explicit typ
is profile-specific and intentionally is not the bare JWT used by some
generic RFC 7523 servers; this provides cross-JWT confusion resistance under
RFC 8725. Unauthenticated exchange is forbidden.
5.4 Prohibited assertion types
v0.1 defines no actor assertion, Connector self-assertion, VC presentation,
or arbitrary platform token type. actor_token and actor_token_type MUST
be absent. A PlatformIdentityCredential v0.2 MUST NOT be reinterpreted as any
assertion above.
6. Token exchange
6.1 Request
The Connector sends an HTTPS POST with media type
application/x-www-form-urlencoded. Every parameter below is single-valued;
a duplicate occurrence MUST be rejected with invalid_request.
| Parameter | v0.1 value |
|---|---|
grant_type | REQUIRED; urn:ietf:params:oauth:grant-type:token-exchange. |
subject_token | REQUIRED; compact JWT from §5.1 or §5.2. |
subject_token_type | REQUIRED; urn:ietf:params:oauth:token-type:jwt. |
resource | REQUIRED exactly once; canonical URL from the Agent Card. |
scope | REQUIRED non-empty, ASCII-space-delimited set from §7. |
requested_token_type | REQUIRED; urn:ietf:params:oauth:token-type:access_token. |
client_id | REQUIRED; Connector DID, equal to both assertion issuers. |
client_assertion_type | REQUIRED; urn:ietf:params:oauth:client-assertion-type:jwt-bearer. |
client_assertion | REQUIRED; compact JWT from §5.3. |
The valid fixture request is equivalent to:
POST /oauth/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&
subject_token=<valid/subject-assertion.json.jwt>&
subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Ajwt&
resource=https%3A%2F%2Fagents.oauth-fixtures.mentionable.dev%2Ffixture-agent%2Fa2a&
scope=a2a%3Amessage.send+a2a%3Atask.read&
requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&
client_id=did%3Aweb%3Aconnector.oauth-fixtures.mentionable.dev&
client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&
client_assertion=<valid/client-assertion.json.jwt>
The angle-bracket references denote the exact compact values embedded in
valid/exchange-request-delegation.json,
not literal wire strings.
The STS MUST authenticate the client assertion and verify the subject
assertion independently, then re-check subject_token.iss == client_id on
the verified claims. Before either assertion can cause DID resolution, it MUST
invoke the receiver-owned policy lookup described in §8.1. Decoding unverified
JOSE fields is used only to construct that lookup candidate and MUST NOT
produce an authorization decision.
6.2 Success response
On success the STS returns the RFC 8693/RFC 6749 response below. The access token string is opaque to the client.
{
"access_token": "<opaque access token>",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"token_type": "Bearer",
"expires_in": 300,
"scope": "a2a:message.send a2a:task.read"
}
access_token, issued_token_type, and token_type are REQUIRED.
issued_token_type MUST be exactly
urn:ietf:params:oauth:token-type:access_token, and token_type MUST be
exactly Bearer in v0.1. When present, expires_in MUST be a positive integer
and scope MUST be a non-empty ASCII-space-delimited set from §7. A client
MUST fail closed on malformed values and MUST NOT use the token if a returned
scope omits any scope required for the requested operation. The STS SHOULD
include expires_in and the granted scope; clients MUST honor both when
present. A generic response parser MAY return a valid granted subset; the
operation-specific caller MUST enforce its required scope before use. Access
tokens MUST be short-lived; 300 seconds is RECOMMENDED and implemented by the
first receiver. A response MUST NOT contain a refresh token.
The response and every error response MUST carry Cache-Control: no-store
and Pragma: no-cache.
6.3 Error mapping and retry
Errors use the RFC 6749 JSON shape, with invalid_target from RFC 8693:
{
"error": "invalid_grant",
"error_description": "subject assertion verification failed"
}
| Condition | Error |
|---|---|
Wrong or unsupported grant_type | unsupported_grant_type |
Missing/duplicate/malformed parameter, missing/wrong requested_token_type, unexpected actor token, issuer/client mismatch, continuation with a message scope | invalid_request |
| Missing/unsupported client auth, invalid client assertion, client identity mismatch | invalid_client |
| Client is not permitted to use token exchange | unauthorized_client |
| Missing, unknown, or unsupported scope | invalid_scope |
| Missing, unknown, non-canonical, duplicate, or substituted resource | invalid_target |
Untrusted/disallowed platform tuple; cryptographic or assertion-profile failure (including expired/replayed/wrong-audience/unauthorized-kid/bad signature); task-binding mismatch | invalid_grant |
| Internal persistence, replay-store, or verifier failure | server_error |
An STS SHOULD return HTTP 401 for invalid_client and HTTP 400 for the other
client errors. It MAY return an opaque correlation identifier, but MUST NOT
echo assertions, tokens, or sensitive claims in the response.
A Connector MUST NOT retry the same request bytes after any recognized OAuth
client error. It may correct the request or mint fresh assertions. Transport
failure, an unparseable 5xx response, or a 5xx server_error MAY be retried with
backoff and fresh assertions. A 5xx server_error is retryable metadata for
the caller; the reference client performs no automatic retry.
The boundary is intentional: failure to decode the JOSE compact serialization
or obtain the minimum request-routing shape is a malformed request and maps to
invalid_request. Once the JWT is structurally decoded, any cryptographic or
assertion-profile failure maps to invalid_grant (except client-assertion
failures, which map to invalid_client).
7. Resource and scope registry
Scopes are case-sensitive and separated only by ASCII space (%x20). A
server MUST reject a token outside this closed v0.1 registry; it MUST NOT
split on generic whitespace after profile validation.
| Scope | Authorized A2A operation | Class |
|---|---|---|
a2a:message.send | Send Message (message/send, SendMessage, or binding-equivalent operation). | Message |
a2a:message.stream | Send Streaming Message (message/stream, SendStreamingMessage, or equivalent). | Message |
a2a:task.read | Get one named task (tasks/get, GetTask, or equivalent). | Task |
a2a:task.cancel | Cancel one named task (tasks/cancel, CancelTask, or equivalent). | Task |
a2a:task.resubscribe | Resubscribe/subscribe to one named task (tasks/resubscribe, SubscribeToTask, or equivalent). | Task |
a2a:task.push-config | Create, get, list, or delete push-notification configuration for one named task. OPTIONAL per deployment. | Task |
An implementation maps these semantic operations consistently across its
A2A JSON-RPC, gRPC, and HTTP+JSON bindings. a2a:task.push-config MUST appear
in scopes_supported only when implemented.
A2A task-listing is not authorized by a2a:task.read in v0.1 because no
request-scoped actor filter is standardized by this profile. A receiver MUST
either reject federated listing or define a later profile version before
offering it.
A platform-subject assertion MAY request message and task scopes together. A task-continuation assertion MUST request only task scopes. A message operation that names an existing task is also subject to the task ownership checks in §8.3, even though its required operation scope is a message scope.
8. Receiver policy and authorization context
8.1 Trust before network fetch
For a platform-subject exchange, the receiver first decodes only enough
unverified data to construct (iss, mentionable_method, sub). It MUST check
that exact tuple together with the exact resource and requested scope set
against the target agent’s local allowed-caller policy before resolving a DID
or performing any issuer-controlled network request. Missing or invalid
mentionable_method MUST fail before policy lookup. A policy miss MUST fail as
invalid_grant.
For a continuation exchange, which intentionally carries no method, the
receiver first looks up the named task locally. The task’s stored profile,
principal, actor, authorization key, resource, and permitted scope set MUST
match the unverified (iss, sub, mentionable_task_id, resource, scopes)
candidate before DID resolution. A missing task ID MUST fail before policy
lookup. Cryptographic verification and verified-claim binding remain REQUIRED
after this prefilter.
The connector-kit reference path exposes this boundary as the REQUIRED,
async-capable authorizeCandidateBeforeFetch callback. It supplies a
discriminated platform or continuation candidate containing the exact raw
strings above. false maps to invalid_grant; a thrown policy-store error may
propagate for the STS to map to server_error. Callback approval is not final
authorization and MUST NOT be used without the subsequent verified result.
8.2 Derived context and access-token binding
After complete verification, the STS derives:
{
"purpose": "delegation",
"principal": "slack:T0123456/U0456789",
"actor": "did:web:connector.oauth-fixtures.mentionable.dev"
}
A continuation exchange additionally derives the verified task binding:
{
"purpose": "delegation",
"principal": "slack:T0123456/U0456789",
"actor": "did:web:connector.oauth-fixtures.mentionable.dev",
"task_id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301"
}
These objects match the expected.authorization values in the two valid
exchange fixtures. act is a semantic output: the Resource Server MUST
retain the Connector as actor. The access token may be self-contained or
opaque, but the AS and Resource Server MUST securely associate it with:
- the target agent and exact canonical resource;
- the granted scope set;
- the profile URI;
- the normalized principal and Connector actor;
- the exact receiver-policy authorization key; and
task_id, when derived from a continuation assertion.
Token bytes and assertion jti values MUST NOT be task ownership keys.
The component implementing the policy callback MUST preserve the exact local
authorization key it matched and bind that key to the issued token and any
created task; the reference verifier’s boolean callback result does not carry
or reconstruct that receiver-owned key.
8.3 A2A operation authorization and task continuity
The Resource Server MUST authenticate the bearer token on every protected A2A operation and enforce the token’s resource and required scope.
When a message creates a task, the server MUST atomically bind the task to the
verified (principal, actor, profile, authorization key). For every later
operation on a named task, including a message that continues it, the server
MUST require all of:
- the token is valid for the same agent resource and carries the operation’s required scope;
- token principal equals the task’s bound principal;
- token actor equals the task’s bound actor;
- token authorization key equals the task’s bound authorization key and that receiver-owned policy remains active; and
- if the token has a
task_id, it exactly equals the operated task.
The principal check applies even to a platform-subject token that carries task scopes but no token-level task binding. Actor-only matching would allow one user behind a multi-user Connector to access another user’s task and is non-conformant.
Knowledge or possession of a task ID or context ID never relaxes these checks. Access-token renewal does not break continuity when the newly issued token has the same verified principal, actor, resource, authorization key, and sufficient scopes.
9. Lifecycle, renewal, and revocation
There are no refresh tokens. Renewal is a new RFC 8693 exchange with a fresh client assertion and either:
- a fresh platform-subject assertion rooted in a new platform event for message scopes; or
- a freshly minted task-continuation assertion rooted in the persisted origin binding for task-only scopes.
The Connector MUST cache access tokens no longer than the returned
expires_in and SHOULD keep a safety margin. It MUST NOT cache or replay
assertions.
Removing or narrowing the exact receiver authorization MUST make outstanding access tokens issued under that authorization unusable and MUST prevent further operations on tasks bound to it. This must be consistent across Resource Server instances. Re-adding the same tuple authorizes new exchanges but MUST NOT reactivate a previously revoked token or historical task binding.
Key rotation and policy revocation are independent. A still-valid signature under a published key cannot override removed receiver policy, and removing a key does not itself create or expand caller policy.
10. PlatformIdentityCredential v0.2 compatibility
PlatformIdentityCredential v0.2 and this profile may use the same Connector DID and Ed25519 key material, but they have different purposes and wire bindings:
| Property | PlatformIdentityCredential v0.2 | OAuth federation v0.1 |
|---|---|---|
| Purpose | Context and attribution only | Authorization for one agent resource and scope set |
| Carrier | message.metadata.mentionable.verifiable_credentials | RFC 8693 token endpoint, then bearer header |
| Binding | Outer messageId challenge and recipient domain | Token-endpoint aud, canonical resource, scopes, and task binding |
| Verification method | Multikey / publicKeyMultibase | JsonWebKey / publicKeyJwk |
| Authorization effect | None by itself | Only after receiver policy and token exchange |
A receiver MUST NOT silently promote a credential into an OAuth assertion,
accept it as subject_token, or infer authorization from its presence.
Conversely, raw OAuth assertions and bearer tokens MUST NOT be inserted into
the VC carrier, A2A message metadata, task history, or backend prompts.
A Resource Server MAY expose a normalized attestation derived from the verified tuple to its agent runtime for audit/context, but it MUST omit the raw JWT, signature, access token, private claims, and replay identifiers.
11. Security and privacy considerations
- Confused deputy and cross-agent reuse: enforce the exact RFC 8707 resource at exchange and at every operation. A token for one agent MUST NOT authorize another.
- Stolen assertion: require verified
subject_token.iss == client_idand authenticate that client with its own freshprivate_key_jwt. - Replay: consume both subject and client
(iss, jti)tuples atomically across instances before issuing a token. - Cross-JWT confusion: pin three distinct
typvalues and their type-specific claims. Never accept one slot’s JWT in another slot. - Issuer SSRF: match receiver-owned policy before resolving any issuer, then apply HTTPS, redirect, address-range, response-size, and timeout controls to DID retrieval.
- Key/controller binding: require DID document
id == iss, exactkidmembership inassertionMethod, matching controller, Ed25519 JWK shape, and a verified EdDSA signature. - Audience/resource substitution: bind both assertions to the exact token endpoint and bind the exchange/token to exactly one canonical resource.
- Multi-user Connectors: match both principal and actor on every task
operation and honor continuation
task_id. - Connector trust: a compromised Connector can forge assertions for subjects inside namespaces the receiver authorized. Keep grants narrow by issuer, method, subject, resource, and scope; revoke them promptly.
- Bearer leakage: use TLS, short lifetimes, no-store responses, and redacted logs. Tokens MUST NOT appear in URLs.
- Data minimization: assertions contain only stable subject, method, token endpoint, time/replay claims, and (for continuation) a task ID. They MUST NOT contain platform tokens, emails, raw provider objects, message bodies, or unnecessary profile data.
- Audit: record a correlation ID, profile, target agent, decision, granted scopes, normalized principal/actor or privacy-preserving derivatives, and policy revision. Do not log compact JWTs, access tokens, client assertions, private keys, or raw proofs.
DPoP under RFC 9449 is the named future sender-constrained hardening path. It is not part of v0.1 conformance; v0.1 access tokens are bearer tokens.
12. Conformance
An issuer/client implementation conforms when it:
- recognizes the extension URI exactly and validates both params;
- discovers and validates the metadata capabilities;
- mints the exact JWT types and claims in §5 under an authorized JWK method;
- sends the exact form profile in §6 without actor tokens;
- uses a fresh event for message assertions and a stored origin binding for continuation assertions; and
- treats OAuth client errors as non-retryable without changing the request.
An STS/Resource Server implementation conforms when it:
- passes every applicable committed conformance fixture;
- applies exact caller policy through a required pre-fetch gate before any issuer-controlled resolution and preserves the matched local authorization key for token/task binding;
- authenticates the client and subject assertion independently;
- enforces verified issuer/client equality, replay, audience, resource, scope, and type-specific claims;
- derives exactly the principal/actor/task context in §8.2;
- binds issued authority and tasks as required by §§8–9; and
- keeps raw assertions, proofs, and tokens out of task and agent-visible data.
The fixture manifest includes accepted platform, continuation, client, and
exchange cases plus expired, not-yet-valid, zero/negative/excessive-TTL,
wrong audience, wrong type/algorithm, invalid method URI, issuer-external
kid, malformed/private/mixed DID verification methods, unauthorized key,
tampered signature, replayed jti, issuer/client mismatch,
undecodable/missing issuer, duplicate/substituted resource,
continuation/message-scope confusion, missing task ID, missing/empty scope,
wrong/missing requested token type, and unknown scope failures. A cache-less
single-assertion verifier may use the fixtures as
a cryptographic component test but MUST NOT claim STS conformance or replay
safety. The combined verifyTokenExchange reference path requires a replay
cache at both its type and runtime boundaries and cannot produce a successful
exchange without it.
Reference implementations:
- issuer, client, verifier, discovery helpers, typed registry, and fixture
generator:
@mentionable/connector-kit; - Slack Connector integration:
examples/slack-connector; - first independent receiver implementation: vicoop-bridge OAuth federation documentation, with remaining reference-path synchronization tracked in planetarium/vicoop-bridge#493.
13. Deferred items
The following are explicitly outside v0.1 and require a later version:
- OAuth presenter/client different from the assertion issuer;
- actor different from the authenticated client,
actor_token, arbitrary actor authorization, gateways/brokers, and multi-hopactchains; - actor-less authentication of relay-verified platform subjects;
- VC presentations or other subject-token types;
- refresh tokens and task-scoped down-exchange;
- DPoP, mTLS, or another sender-constrained access-token profile;
- agent-DID anchoring independent of Agent Card publication;
- federated task listing;
- a standardized pattern/policy language beyond exact caller tuples; and
- upstream standardization of this acquisition mechanism in A2A itself.
This profile reverses the archived
draft2-policy-and-mandates.md
decision to reuse only the RFC 8693 act claim shape while rejecting its
token endpoint. That archived MandatePart design remains non-normative; this
profile adopts RFC 8693 specifically for the authorization lifecycle.
14. References
Normative:
- RFC 2119 — Key words for use in RFCs
- RFC 8174 — Ambiguity of Uppercase vs Lowercase
- RFC 6749 — OAuth 2.0 Authorization Framework
- RFC 7523 — JWT Profile for OAuth 2.0 Client Authentication
- RFC 8414 — OAuth 2.0 Authorization Server Metadata
- RFC 8693 — OAuth 2.0 Token Exchange
- RFC 8707 — Resource Indicators for OAuth 2.0
- RFC 8725 — JWT Best Current Practices
- W3C DID Core
- did:web Method Specification
- A2A Protocol Specification
Informative: