§1 · The Trust + Identity Layer Under UCP
The layer every other spoke assumes and none of them build.
Every AgentMall spoke that touches authentication — the 5 platforms, the api/mcp/ucp triad, the /agents page — references "OAuth 2.1 + PKCE + RFC 8414 metadata" and stops at the surface. This spoke goes deep on the agent-specific spec chain: WHO is being authorized (the agent or the principal?), HOW spending limits get expressed (RFC 9396 Rich Authorization Requests, NOT scope strings), HOW a user kills agent authority in real time (RFC 7009 plus grace-period semantics), and HOW agents prove their own identity (RFC 7521 JWT assertion plus RFC 9449 DPoP). Name the RFC stack as the spine and the rest of this article writes itself: OAuth 2.1 baseline → PKCE (RFC 7636) → DCR (RFC 7591) → metadata discovery (RFC 8414) → revocation (RFC 7009) → token exchange (RFC 8693) → RAR (RFC 9396) → DPoP (RFC 9449) → the MCP authorization profile (2025-03-26). The AgentMall 4-Layer Model — Structured Data → API Endpoint → MCP Tool Description → UCP Compatibility — silently assumes a working identity and trust layer underneath all four. That layer is this article.
§2 · The Agent-Authentication Problem
"Use OAuth 2.0" is not an answer. It is a shrug.
A human signs into your store and a session cookie carries their identity for an hour. That model breaks the moment a non-human actor — an agent running in ChatGPT, Claude Desktop, or a back-office cron — wants to transact. An agent is not a person. It holds a credential a person delegated to it, it may act for days unattended, and it can be cloned, leaked, or pointed at the wrong merchant. "Just use OAuth 2.0" tells you nothing about the four questions an operator actually has to answer before letting an agent spend money.
Question 1 · WHO
Who is authorized?
The agent, the human principal, or the software client? The authorization code flow (RFC 6749) authorizes a client to act for a principal; client_credentials authorizes the client as itself. Get this wrong and your audit trail is fiction.
Question 2 · HOW MUCH
What are the spending limits?
"Up to $500 per transaction, capped at $2,000 per month, groceries only." A scope string cannot hold a number. RFC 9396 Rich Authorization Requests is the only standard primitive that can — the centerpiece of this spoke.
Question 3 · WHEN
Is the authority time-bound?
Tokens expire; grants should too. Short-lived access tokens plus a refresh token under a documented expiry (and an authorization_details expiresAt per RFC 9396) bound how long an agent can act before re-consent.
Question 4 · HOW TO REVOKE
How do you kill it now?
The user wants the agent's authority gone immediately. RFC 7009 revocation is the endpoint; the real answer is revocation plus cache invalidation plus introspection (RFC 7662) for high-value flows. There is no instant default.
The Rest of This Article
The four questions map one-to-one to the RFC stack. WHO is the grant-type decision (authorization_code vs client_credentials) plus identity attestation (RFC 7521 + RFC 9449). HOW MUCH is RFC 9396 RAR. WHEN is token lifetime and grant expiry. HOW TO REVOKE is RFC 7009 + RFC 7662. Everything below is the agent-specific spec chain that turns "use OAuth" into something an operator can ship. A spoke that says "use OAuth 2.0" and stops there is a failed spoke.
§3 · The RFC Stack at a Glance
Ten specifications. One agent commerce trust layer.
Each RFC below answers a slice of the four questions. OAuth 2.1 (draft-ietf-oauth-v2-1 — re-verify status before launch, it is still a working group draft) is the consolidated baseline: mandatory PKCE for all clients, no implicit grant, no ROPC. PKCE (RFC 7636) adds the code_verifier / code_challenge proof that stops authorization-code interception. DCR (RFC 7591) lets an agent that has never seen your server register itself and get a client_id. Metadata (RFC 8414) is the /.well-known/oauth-authorization-server document agents read to discover every other endpoint. Revocation (RFC 7009) is the /revoke kill switch. Token Exchange (RFC 8693) lets one agent delegate to another with on_behalf_of / act_as semantics. RAR (RFC 9396) carries fine-grained spending limits in authorization_details. DPoP (RFC 9449) binds a token to a client-held key so a leaked bearer token is useless. RFC 7521 is the JWT client-assertion framework for agent identity. And the MCP Authorization profile (2025-03-26) tells you which of these MCP requires versus recommends.
The chain, mapped to agents
| RFC / Spec | What It Provides | Why It Matters for Agents | Required by MCP? |
| OAuth 2.1 (draft) | Consolidated framework; mandatory PKCE; implicit + ROPC removed | The baseline every agent grant builds on; one human-auth flow, no legacy grants | MUST implement OAuth 2.1 |
| RFC 7636 · PKCE | code_verifier + code_challenge; S256 method | Stops code interception for public agent clients; required for ALL clients in 2.1 | REQUIRED for all clients |
| RFC 7591 · DCR | Self-service client registration; /register endpoint | An agent that has never seen your AS can register and proceed without manual config | SHOULD support |
| RFC 8414 · Metadata | /.well-known/oauth-authorization-server discovery | Agents discover your endpoints instead of hardcoding; survives URL rotation | Servers SHOULD; clients MUST |
| RFC 7009 · Revocation | The /revoke endpoint | How a user kills agent authority; foundation of the kill switch | SHOULD (recommended) |
| RFC 8693 · Token Exchange | on_behalf_of / act_as delegation | Agent-to-agent: a buyer-agent's token becomes a downstream call without re-consent | Optional (not in core profile) |
| RFC 9396 · RAR | authorization_details structured policy | THE spending-limit primitive; encodes max amount, period, merchant scope, expiry | Optional (forward-looking) |
| RFC 9449 · DPoP | Proof-of-possession bound tokens | A leaked bearer token is exploitable; DPoP binds it to the agent's key | Acknowledged, not mandated |
| RFC 7521 · Assertions | JWT client assertion framework | Cryptographically attestable agent identity at the token endpoint | Optional (auth method) |
| MCP Auth (2025-03-26) | The MCP OAuth profile | Defines the required floor for MCP agent servers and clients | The profile itself |
Where The Manifest Declares This
Which of these flows YOUR server supports is declared in the /agents.json capability manifest — the auth object names the authorization_endpoint, token_endpoint, registration_endpoint, and revocation_endpoint so an agent knows your spec chain before it sends a single request. That manifest format lives on the /agents page spoke; this spoke is the deep specification of the flows it points at.
Critical · Re-verify Before Launch
All RFC version numbers and dates, the MCP authorization spec revision (currently 2025-03-26), and the OAuth 2.1 draft status (draft-ietf-oauth-v2-1 is a working group draft, not yet a Standards Track RFC) are volatile. Confirm each against the current IETF documents and the latest MCP authorization specification before you build against them.
§4 · Authorization Code + PKCE (RFC 7636)
The human-in-the-loop agent purchase flow, end to end.
When a human authorizes an agent to act on their behalf for the first time, this is the flow. It is the only human-authorization flow OAuth 2.1 wants you to build. Per OAuth 2.1, PKCE (RFC 7636) is mandatory for ALL clients — including confidential clients, not just public mobile apps — and the implicit grant is gone, so there is no shortcut to skip it. The agent proves it is the same party that started the flow by binding the authorization request to a secret it never puts on the wire.
The six steps
| # | Step | What Happens |
| 1 | Generate verifier + challenge | Agent creates a random code_verifier, then code_challenge = BASE64URL(SHA256(code_verifier)) per RFC 7636 §4.2 |
| 2 | Authorize URL | Agent redirects the user to the authorization_endpoint with response_type=code, code_challenge, and code_challenge_method=S256 |
| 3 | Login + consent | User authenticates with the AS and approves the requested authority (scopes and — per §6 — authorization_details) |
| 4 | Redirect with code | AS redirects back to the agent's registered redirect_uri with a one-time authorization code |
| 5 | Token exchange | Agent POSTs the code plus the original code_verifier to the token_endpoint; the AS verifies the SHA-256 of the verifier matches the challenge |
| 6 | Bearer (or DPoP) token | AS returns an access_token (plus refresh_token); the agent sends it in the Authorization: Bearer header — or DPoP-bound per §8 |
Critical · S256 Only
The S256 code_challenge_method is the only one you should accept. The plain method (where the challenge equals the verifier in cleartext) is deprecated and offers no real protection against interception. Reject any authorize request that sends code_challenge_method=plain — do not silently downgrade.
The token exchange (step 5) — real curl
The verifier is sent only at this step, over TLS, directly to the token endpoint. The AS recomputes SHA256(code_verifier), compares it to the stored challenge, and issues the token only on a match.
curl -X POST "https://your-as.example.com/oauth2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=SplxlOBeZQQYbYS6WxSbIA" \
-d "redirect_uri=https://buyer-agent.example.com/callback" \
-d "client_id=s6BhdRkqt3" \
-d "code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
Token response (200 OK)
{
"access_token": "2YotnFZFEjr1zCsicMWpAA",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
"scope": "products:read orders:read"
}
The agent then carries that token on every request to a resource server. In MCP, this is the Bearer token sent over the streamable HTTP transport. For how that token rides the MCP HTTP envelope and how an MCP server validates it, see the MCP spoke — this spoke does not re-explain the MCP transport, only the OAuth that secures it.
§5 · Client Credentials Flow
The operator-side autonomous agent. No human in the loop.
Not every agent acts for a shopper. Your own back-office agent — the one that reconciles orders overnight, watches inventory thresholds, or rebuilds your product feed — has no human in the loop. For that actor, the client_credentials grant (RFC 6749 §4.4) is correct: the software client authenticates as itself and gets a token scoped to exactly what it needs, nothing more.
The request
curl -X POST "https://your-as.example.com/oauth2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "scope=products:read orders:read" \
-d "client_id=backoffice-monitor" \
-d "client_secret=SECRET_FROM_VAULT"
Tip · Scope Narrowly
A monitoring agent gets products:read orders:read — and never orders:write. If the agent only reads, it should be structurally incapable of writing. Scope strings are fine here precisely because there is no spending authority to encode; a read-only back-office agent does not need RFC 9396 RAR. Reserve RAR for the flows that move money.
Decision criteria — which grant?
| Situation | Grant | Principal in the Token |
| Shopper connects account to a buyer-agent | authorization_code + PKCE | The human user (agent acts on behalf of) |
| Operator's overnight reconciliation agent | client_credentials | The software client itself |
| Inventory / price monitoring agent | client_credentials (read-only scope) | The software client itself |
| Agent buying on a named user's behalf with a cap | authorization_code + PKCE + RAR | The human user, with RFC 9396 limits |
| One agent calling another downstream | Token Exchange (RFC 8693) | Preserved or substituted — see §9 |
The CFO-agent persona — a procurement agent that authorizes purchases against a department budget with no human approving each line — is the canonical client_credentials-plus-RAR case for B2B. That persona and its budget-enforcement model live on the B2B Procurement spoke.
The 30-Day AgentMall Newsletter
One operator note per week. The trust layer in your inbox.
RFC-grounded patterns, real failure modes, and the next AgentMall spoke as it ships. No fluff. Cancel any time.
§6 · Rich Authorization Requests (RFC 9396) — The Centerpiece
The spending-limit primitive. This is the whole article.
This is the section that makes the spoke operator-actionable instead of theoretical. The single hardest practical question an operator asks about agent commerce is "how do I cap what an agent can spend?" — and the honest answer is that a scope string cannot do it. A scope is a freeform, space-delimited list of capability names. It can say orders:write. It cannot say "up to $500 per transaction, $2,000 per calendar month, groceries only, expiring December 31, 2026" without inventing a non-standard, brittle scope syntax that every agent and every AS would then have to special-case. RFC 9396 Rich Authorization Requests is the canonical, standardized answer.
How RAR works
RAR adds a single new request parameter — authorization_details — that carries a JSON array of structured policy objects. It augments or replaces the scope string with machine-readable, AS-enforceable authority. Per RFC 9396 §2, each object has a type whose value is URI-namespaced, and the authorization server defines the schema for each type. The spec ships with common types like payment_initiation, account_information, and openid, and allows ad-hoc types the AS defines. The granted details come back in the token response so the agent (and any auditor) can see exactly what authority was issued.
A real authorization_details object for an agent spending cap
Here is a complete, runnable authorization_details array for a buyer-agent authorized to spend up to $500 USD per transaction, capped at $2,000 USD per calendar month, only at merchants in the "groceries" category, expiring at the end of 2026. This is the object you send on the authorize request and that the AS enforces on every token-bearing transaction.
{
"authorization_details": [
{
"type": "https://agentmall.example/auth/purchase-authority",
"locations": ["https://api.your-store.example/v1"],
"actions": ["initiate_checkout", "complete_payment"],
"datatypes": ["order", "payment"],
"identifier": "buyer-agent-7f3a",
"maxAmount": {
"perTransaction": { "value": "500.00", "currency": "USD" },
"perPeriod": { "value": "2000.00", "currency": "USD", "period": "P1M" }
},
"merchantCategories": ["groceries"],
"currency": "USD",
"expiresAt": "2026-12-31T23:59:59Z"
}
]
}
Why The Field Names Matter
Per RFC 9396 §2, type, locations, actions, and datatypes are the spec-defined common fields; identifier identifies the resource owner or subject; and everything else (maxAmount, merchantCategories, currency, expiresAt) is type-specific schema YOUR authorization server defines under your URI-namespaced type. The AS is what enforces the numbers — RAR is the standard envelope that carries them and the contract the AS publishes for its types. Document your type schema so agents can construct valid requests.
On the authorize request
The authorization_details parameter rides the same authorization code + PKCE flow from §4. URL-encoded, the authorize request looks like this (line-wrapped for readability):
GET /oauth2/authorize?response_type=code
&client_id=buyer-agent-7f3a
&redirect_uri=https%3A%2F%2Fbuyer-agent.example.com%2Fcallback
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
&authorization_details=%5B%7B%22type%22%3A%22https%3A%2F%2Fagentmall...%7D%5D
HTTP/1.1
Host: your-as.example.com
Why operators MUST adopt RAR for agent commerce
Without RAR, you have two bad options: invent a non-standard scope format (and break interoperability with every agent that does not know it), or enforce caps entirely in application code with no signal in the grant itself (and lose the audit trail). RAR is the third, correct option — the cap is part of the issued authority, visible in the token response, enforceable at the AS, and standardized so any conforming agent can request it. A scope string cannot express spending limits; RFC 9396 RAR is the canonical answer, full stop.
Real-world status
RAR is not theoretical. The Berlin Group openFinance framework, Open Finance Brazil, and the FAPI 2.0 security profile already require or build on RAR for regulated payment use cases. On the implementation side, Keycloak, Auth0, and Okta have shipped or announced RAR support in their authorization servers — re-verify the exact support level and any pricing tier on each vendor's current docs before launch, as capability sets change frequently.
Cross-link · UCP Spending Cap
The UCP spending cap you may have seen on the protocol spoke is, operationally, a RAR authorization_details object. UCP gives it a friendly name in the agent profile; underneath, it is RFC 9396. For the UCP-layer view of the cap and how it surfaces in the eight-step checkout state machine, see the UCP spoke.
§7 · Dynamic Client Registration (RFC 7591)
Agents that register themselves.
The other half of MCP's discovery story: an agent that has never seen your authorization server should be able to register itself, get a client_id, and proceed — without you manually provisioning a client for every agent runtime on earth. RFC 7591 Dynamic Client Registration defines the /register endpoint that makes this possible, and you declare it in your RFC 8414 metadata as registration_endpoint so agents can find it.
The registration POST
curl -X POST "https://your-as.example.com/oauth2/register" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer INITIAL_ACCESS_TOKEN" \
-d '{
"client_name": "Acme Buyer Agent",
"redirect_uris": ["https://buyer-agent.example.com/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none",
"scope": "products:read orders:read orders:write",
"software_statement": "eyJhbGciOiJSUzI1NiJ9.eyJzb2Z0d2FyZV9pZCI6..."
}'
Registration response (201 Created)
{
"client_id": "s6BhdRkqt3",
"client_id_issued_at": 1748908800,
"redirect_uris": ["https://buyer-agent.example.com/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"token_endpoint_auth_method": "none"
}
Critical · The DCR Gating Problem
Do you accept ANY registration request? No. An open /register endpoint is an abuse magnet. Per RFC 7591 §3.1.1, most production operators require either a software_statement — a signed JWT that vouches for the client, issued by a party you trust — or DCR-with-Initial-Access-Token, where the registration request itself must carry a bearer token you issued out of band. Pick one and reject everything that fails the gate.
When to enable DCR vs. require manual registration
Enable DCR when you want to be reachable by the open agent ecosystem — any conforming MCP client should be able to onboard. Require manual registration when you have a small, known set of partner agents and you would rather provision them by hand. The MCP authorization spec (2025-03-26 — re-verify before launch) puts DCR at SHOULD, not MUST, so it is a strong recommendation rather than a hard requirement. The manifest declares your auth.registration_endpoint so agents know whether self-registration is even an option; that field is documented on the /agents page spoke.
§8 · Token Revocation (RFC 7009) + The Kill Switch
Killing agent authority — and why it is not instant.
The user clicks "Revoke this agent's access." What actually happens? Per RFC 7009, the client (or your UI's backend) POSTs the token to the /revoke endpoint, and the AS marks the refresh_token — and typically its associated access_tokens — as invalid. That is the easy part. The hard part is propagation.
The revoke request
curl -X POST "https://your-as.example.com/oauth2/revoke" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=tGzv3JOkF0XG5Qx2TlKWIA" \
-d "token_type_hint=refresh_token" \
-d "client_id=buyer-agent-7f3a"
Critical · The Grace-Period Reality
Revocation is NOT instantaneous in all AS implementations. A resource server that validates access tokens locally — checking a JWT signature and exp claim without calling back to the AS — will keep honoring a revoked-but-unexpired token until it expires or its cached validity TTL lapses. That gap can be seconds or minutes depending on your architecture. Real revocation propagation depends on whether your resource servers introspect or trust local validity.
The user-facing "Kill agent authority NOW" button
For agent commerce, the button must do more than call /revoke. It must (1) revoke the refresh token at the AS, and (2) signal every resource server the agent can reach to drop any cached validity for that grant. Document this two-part behavior for your operators — a one-call implementation that only hits the AS will leave a window where the agent can still transact. For high-value flows, the resource server should check the introspection endpoint (RFC 7662) or a revocation list on each sensitive request rather than trusting a cached token.
DPoP and the token-binding story
A revoked token still carries valid cryptographic proof of possession via DPoP — the signature is mathematically fine. That is fine, because a properly implemented resource server checks the revocation status (via introspection per RFC 7662, or a revocation list) before accepting the token, regardless of how well-formed the DPoP proof is. Belt and suspenders: DPoP proves the caller holds the key; introspection proves the token is still live. You need both for high-value agent transactions.
Your manifest should declare auth.revocation_endpoint so agents — and the user-facing tooling that manages them — know where the kill switch lives. That field is part of the manifest schema on the /agents page spoke.
§9 · Token Exchange (RFC 8693)
Agent-to-agent delegation. on_behalf_of vs act_as.
When one agent calls another, you have a delegation problem. A buyer-agent in Claude Desktop calls a merchant MCP server, which then needs to call a downstream payment processor on behalf of the original user. You do not want to re-prompt the human for consent at every hop. RFC 8693 Token Exchange is the standard mechanism: a service exchanges a token it received for a new token it can use downstream.
The request
The grant type is the long URN urn:ietf:params:oauth:grant-type:token-exchange. The service presents the inbound token as the subject_token and asks for a token it can use against the downstream resource.
curl -X POST "https://your-as.example.com/oauth2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
-d "subject_token=2YotnFZFEjr1zCsicMWpAA" \
-d "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \
-d "audience=https://payments.processor.example/v1" \
-d "requested_token_type=urn:ietf:params:oauth:token-type:access_token"
Exchange response (200 OK)
{
"access_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImF0K2p3dCJ9...",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"token_type": "Bearer",
"expires_in": 300,
"scope": "payment:initiate"
}
on_behalf_of vs act_as
| Semantic | What It Means | Delegation Chain |
on_behalf_of | The downstream call is made for the original principal; the calling agent is a delegate | Preserved — the principal's identity stays visible end to end |
act_as | The calling agent substitutes its own identity as the actor | Collapsed — the downstream sees the agent, not the chain |
Critical · Pick One
Do not combine on_behalf_of and act_as semantics in a single token exchange — the result is undefined. Choose based on your audit requirements: use on_behalf_of when you need the full delegation chain visible (most regulated payment flows want this), and act_as when the downstream only needs to trust the immediate caller.
When to use Token Exchange vs. a fresh authorization code flow
Use Token Exchange when the calling agent already holds a valid token and you want to delegate downstream without re-prompting the user. Use a fresh authorization code flow (§4) when you need new human consent — for example, when the downstream authority exceeds what the original grant covered. For the MCP authorization spec's stance on token handling across MCP servers, see the MCP spoke.
§10 · DPoP (RFC 9449)
Binding tokens to specific agent clients. A leaked bearer token is worthless.
Why are bearer tokens insufficient for agent identity? Because a bearer token grants its authority to ANY holder. If an agent's token leaks — logged in plaintext, intercepted, exfiltrated from a compromised host — whoever holds it can spend under the agent's grant until it expires. For high-value agent commerce, that is unacceptable. DPoP (RFC 9449, Demonstrating Proof-of-Possession) fixes it by binding the access token to a cryptographic key the agent controls.
How DPoP works
On each request, the agent signs a short-lived JWT with its private key and sends it in a DPoP header alongside the access token. The token itself carries a cnf (confirmation) claim with the thumbprint of the agent's public key. The resource server verifies the DPoP proof signature, confirms the bound public key matches the token's cnf claim, and only then accepts the request. A thief who steals only the token cannot forge the proof without the private key.
The DPoP proof header structure
{
"typ": "dpop+jwt",
"alg": "ES256",
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "l8tFrhx-34tV3hRICRDY9zCkDlpBhF42UQUfWVAWBFs",
"y": "9VE4jf_Ok_o64zbTTlcuNJajHmt6v9TDVrU0CdvGRDA"
}
}
.
{
"jti": "e1j3V_bKic8-LAEB",
"htm": "POST",
"htu": "https://api.your-store.example/v1/checkout",
"iat": 1748908800
}
The header carries jwk (the agent's public key), alg (signing algorithm), and typ=dpop+jwt. The payload carries htm (the HTTP method), htu (the target URI), iat (issued-at), and jti (a unique ID to prevent replay). Binding the method and URI into the proof means a captured DPoP header cannot be replayed against a different endpoint.
Server-side validation
- Verify the DPoP proof JWT signature against the embedded
jwk.
- Confirm
htm and htu match the actual request method and URL.
- Check the JWK thumbprint matches the access token's
cnf.jkt claim.
- Confirm the
jti has not been seen before (replay protection) and iat is fresh.
- Only then — and only after a revocation/introspection check — accept the request.
Critical · Required For High-Value Flows
For agent commerce, DPoP is how you prevent a leaked agent token from being used by a third party. Require it for high-value or sensitive transactions — anything that moves money. The MCP authorization spec acknowledges proof-of-possession but does not yet mandate DPoP (re-verify against the latest spec version before launch). Do not publish any claim that bearer tokens alone are sufficient for high-value agent commerce; they are not.
DPoP is also a fraud-mitigation primitive — the same key-binding that stops token replay underpins agent-level fraud signals. The forthcoming Fraud Prevention spoke (/agentmall_spoke_fraud_prevention, future Trust batch) will reference DPoP in that role.
§11 · Implementation
Stripe MCP and your own authorization server. Do not roll your own from scratch.
The canonical live reference for agent OAuth today is mcp.stripe.com. It publishes a real RFC 8414 Authorization Server Metadata document you can fetch right now and read end to end. It is the best worked example of what the metadata floor looks like in production.
Stripe's live metadata document
Fetch https://mcp.stripe.com/.well-known/oauth-authorization-server and you get a verified RFC 8414 document. As observed at the time of writing (re-verify the live values before launch):
{
"issuer": "https://access.stripe.com/mcp",
"authorization_endpoint": "https://access.stripe.com/mcp/oauth2/authorize",
"token_endpoint": "https://access.stripe.com/mcp/oauth2/token",
"registration_endpoint": "https://access.stripe.com/mcp/oauth2/register",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none"]
}
Read that document against this spoke and it confirms the floor: authorization_code + refresh_token grants, S256 PKCE only (no plain), and a registration_endpoint for RFC 7591 Dynamic Client Registration. The token_endpoint_auth_methods_supported: ["none"] value tells you it is built for public clients that authenticate via PKCE rather than a client secret — exactly the agent case.
What Stripe Does NOT Yet Expose
The Stripe MCP metadata does not advertise RAR via authorization_details (RFC 9396), DPoP (RFC 9449), or Token Exchange (RFC 8693). These are the forward-looking adds for fine-grained spending limits, token binding, and agent-to-agent delegation — important to note, because Stripe is a strong reference for the basics but not a complete reference for the full agent spec chain. Re-verify the live metadata before launch, as vendor capability sets change frequently.
Roll-your-own AS options
Do not build an authorization server from scratch. Use a battle-tested implementation. RAR and DPoP support and pricing vary across vendors — re-verify all of the following on each vendor's current docs before launch:
| Option | Type | Notes (re-verify before launch) |
| Keycloak | OSS, Java-based | Self-hosted; RAR and DPoP support announced/shipping — re-verify version and config before launch |
| Auth0 | SaaS (Okta now) | Hosted; RAR support available on certain tiers — re-verify tier, pricing, and DPoP status before launch |
| Okta | SaaS | Hosted enterprise IdP; re-verify RAR + DPoP support and pricing before launch |
| Ory Hydra | OSS, Go-based | Self-hosted OAuth 2.1/OIDC server; re-verify RAR + DPoP support before launch |
The MCP authorization spec gates
The MCP authorization spec (2025-03-26 — re-verify before launch) sets a clear floor for any AS that fronts an MCP server. Confirmed against the published specification:
- OAuth 2.1 — MUST. Implementations must implement OAuth 2.1 for both confidential and public clients.
- PKCE — REQUIRED for all clients. No exceptions, including confidential clients.
- RFC 8414 metadata — servers SHOULD, clients MUST. Clients must follow the metadata protocol; servers that do not publish it MUST support the default
/.well-known/oauth-authorization-server URI.
- RFC 7591 DCR — SHOULD. Strongly recommended so agents can self-register, but not mandatory.
Tip · Practical Advice
Start with Keycloak if you want OSS and full control, or Auth0/Okta/Ory if budget and managed hosting suit you better. Publish your RFC 8414 metadata first, get PKCE-only authorization code working, then layer in RAR for spending limits and DPoP for binding. Building an authorization server from scratch is a multi-quarter project with a large security surface — there is no reason to do it.
§12 · Common Mistakes
Eight ways agent OAuth breaks in production.
1. Using the plain code_challenge_method instead of S256
The plain method sends the verifier in cleartext as the challenge and offers no real protection against authorization-code interception. The fix: S256 is the only code_challenge_method you should accept. Reject any authorize request with code_challenge_method=plain outright — do not silently downgrade.
2. Treating scope as sufficient for spending limits
A scope is a freeform space-delimited string; it can name a capability but it cannot encode "max $X per transaction." The fix: RFC 9396 Rich Authorization Requests is the canonical structured-policy primitive. Put the cap in authorization_details, not in an invented scope format.
3. Skipping RFC 8414 metadata publishing
Without a metadata document, agents discover nothing — they hardcode your endpoints and break the moment you rotate URLs. The fix: publish /.well-known/oauth-authorization-server per RFC 8414, which the MCP spec requires clients to read, and keep it current with every endpoint you operate.
4. Accepting unsigned Dynamic Client Registration
An open /register endpoint is an abuse magnet — anyone can mint a client. The fix: require a software_statement per RFC 7591 §3.1.1, or gate registration with an Initial Access Token issued out of band. Reject every registration that fails the gate.
5. Caching revocation state with multi-minute TTLs
A resource server that trusts a locally cached "valid" verdict will honor a revoked token for the length of the TTL. The fix: for high-value agent transactions, query the introspection endpoint per RFC 7662 on each sensitive request instead of relying on cached validity, or keep access token lifetimes very short.
6. Storing access tokens client-side without DPoP binding
A plain bearer token stored on an agent host is exploitable by anyone who can read it, for as long as it lives. The fix: for high-value flows, require DPoP per RFC 9449 so the token is bound to a key the agent controls — a leaked bearer token alone becomes useless without the private key.
7. Using the authorization_code grant for back-office agents with no human in the loop
The authorization code flow assumes a human is present to log in and consent; a headless overnight agent has no one to click "approve." The fix: client_credentials is the correct grant type for operator-owned agents, scoped narrowly to exactly what the agent reads or writes.
8. Combining on_behalf_of and act_as semantics in a single token exchange
RFC 8693 defines these as distinct delegation modes; mixing them in one exchange is undefined behavior. The fix: pick one — on_behalf_of preserves chain visibility (use it when you need the full delegation trail), act_as substitutes identity (use it when the downstream only needs to trust the immediate caller).
§13 · FAQ
Frequently asked questions.
What is the difference between OAuth 2.0 and OAuth 2.1 for agent use?
OAuth 2.1 (draft-ietf-oauth-v2-1, still a working group draft — re-verify before launch) consolidates the framework and tightens it for the agent era. The three differences that matter: PKCE (RFC 7636) is now mandatory for ALL clients, including confidential clients, not just public mobile apps; the implicit grant is removed; and the resource owner password credentials (ROPC) grant is removed. For an operator, that means you build exactly one human-authorization flow — authorization code + PKCE with code_challenge_method=S256 — and you never declare support for implicit or ROPC. An agent integration that still uses the implicit grant is building against a deprecated, removed flow.
Why isn't scope good enough for agent spending limits?
A scope is a freeform space-delimited string — products:read orders:write. It can name a capability but it cannot encode a number. There is no standard way to express "max $500 per transaction, $2,000 per calendar month, only at grocery merchants, expiring 2026-12-31" inside a scope string without inventing a non-standard, brittle scope format that every agent would then have to special-case. RFC 9396 Rich Authorization Requests is the canonical answer: the authorization_details field carries a JSON array of structured policy objects with type-namespaced semantics, so the spending cap travels with the grant in a machine-readable, AS-enforceable form. Per RFC 9396 §2, type values are URI-namespaced and the AS defines the schema for each type.
When should I use authorization_code versus client_credentials?
Use authorization_code + PKCE when a human is authorizing an agent to act on the human's behalf — a shopper connecting their account to a buyer-agent. The principal in the token is the user; the agent acts under delegated authority. Use client_credentials (RFC 6749 §4.4) when there is no human in the loop — the operator's own back-office agent that reconciles orders or monitors inventory. The principal is the operator's software client itself, authenticating with its own credentials (or a JWT client assertion per RFC 7521), and you scope it narrowly: products:read, orders:read, never orders:write for a monitoring agent.
Do I need DPoP for every agent transaction?
No — but yes for high-value or sensitive flows. A plain bearer token grants its authority to ANY holder; if it leaks, a third party can spend under the agent's grant until the token expires. DPoP (RFC 9449) binds the access token to a client-controlled cryptographic key, so the resource server can prove the caller is the legitimate holder before honoring the request. For low-value read-only flows the operational cost may not be worth it, but for delegated purchase authority — anything that moves money — require DPoP. The MCP authorization spec acknowledges proof-of-possession but does not yet mandate DPoP (re-verify against the latest spec version before launch).
What does the MCP spec require versus recommend?
Per the MCP authorization specification (2025-03-26 — re-verify before launch), authorization itself is OPTIONAL, but when supported: implementations MUST implement OAuth 2.1 for both confidential and public clients; PKCE is REQUIRED for all clients; MCP servers SHOULD and MCP clients MUST implement RFC 8414 Authorization Server Metadata, and servers that do not MUST support the default /.well-known/oauth-authorization-server URI; and implementations SHOULD support RFC 7591 Dynamic Client Registration. So the hard floor is OAuth 2.1 + PKCE + metadata discovery; DCR is a strong recommendation, not a mandate.
How fast does token revocation actually take effect?
It depends entirely on your authorization server and resource server architecture. RFC 7009 defines the /revoke endpoint, which marks a refresh_token (and typically its associated access_tokens) as invalid at the AS. But a resource server that validates access tokens locally — by checking a JWT signature and expiry without calling back to the AS — will keep honoring a revoked-but-unexpired token until it expires or its cached validity TTL lapses. That gap can be seconds or minutes. For high-value agent flows, do not rely on cached validity: query the introspection endpoint (RFC 7662) on each sensitive request, or keep access token lifetimes very short so revocation of the refresh token takes effect on the next refresh.
Can I use Token Exchange to let one agent call another without a fresh authorization?
Yes. RFC 8693 Token Exchange (grant_type=urn:ietf:params:oauth:grant-type:token-exchange) lets a buyer-agent's token be exchanged for a new token the downstream service can use, without re-prompting the user. The on_behalf_of parameter preserves the delegation chain — the principal's identity stays visible end to end. The act_as parameter substitutes the calling agent's identity, collapsing the chain. Pick one per exchange; combining on_behalf_of and act_as semantics in a single token is undefined behavior. Use Token Exchange when the calling agent already holds a valid token and you want to delegate downstream; use a fresh authorization code flow when you need new human consent.
Is Stripe's mcp.stripe.com a complete reference implementation of agent OAuth?
It is strong on the basics and a useful reference, but not complete for the full agent spec chain. As of this writing, mcp.stripe.com publishes a valid RFC 8414 metadata document at /.well-known/oauth-authorization-server advertising authorization_code and refresh_token grants, S256 PKCE only, and a registration_endpoint for RFC 7591 Dynamic Client Registration. What it does NOT yet expose in that metadata: RAR via authorization_details (RFC 9396), DPoP (RFC 9449), or Token Exchange (RFC 8693). Those are the forward-looking adds for fine-grained spending limits, token binding, and agent-to-agent delegation — re-verify the live metadata before launch, as vendor capability sets change frequently.
§14 · Step-by-Step
The agent OAuth build, in five steps.
Each step mirrors the HowTo JSON-LD at the top of this page word for word. Execute in order. Publish metadata first, lock down PKCE, layer in spending limits, gate registration, then wire the kill switch.
Step 1 — Publish your RFC 8414 Authorization Server Metadata at /.well-known/oauth-authorization-server
Stand up the metadata document agents discover before anything else. Publish a JSON document at https://your-as.example.com/.well-known/oauth-authorization-server declaring issuer, authorization_endpoint, token_endpoint, registration_endpoint, revocation_endpoint, introspection_endpoint, response_types_supported, grant_types_supported, code_challenge_methods_supported (S256 only), and token_endpoint_auth_methods_supported. Per the MCP authorization spec (2025-03-26 — re-verify before launch), MCP clients MUST read this document, so without it agents hardcode endpoints and break when you rotate URLs. Validate the JSON and confirm every advertised endpoint resolves over HTTPS.
Step 2 — Configure PKCE-only authorization code flow (S256 method, no plain) per RFC 7636
Build exactly one human-authorization flow: authorization code with PKCE. Require code_challenge_method=S256 on every authorize request and reject the deprecated plain method outright. Per OAuth 2.1, PKCE is mandatory for ALL clients including confidential clients, and you must not declare support for the implicit or ROPC grants. Verify a full round trip: agent generates a code_verifier and S256 code_challenge, the user logs in and consents, the redirect delivers a code, and the token endpoint returns an access token only when the matching code_verifier is presented.
Step 3 — Enable Rich Authorization Requests (RFC 9396) to express spending limits in authorization_details
Turn on authorization_details support at the authorize and token endpoints. Define a URI-namespaced type for purchase authority and an enforceable schema carrying max amount per transaction, max amount per period, allowed merchant categories, currency, and an expiry. Per RFC 9396 §2, type values are URI-namespaced and the AS defines each schema, so the spending cap travels with the grant in machine-readable form instead of a brittle scope string. Confirm the AS enforces the cap on every token-bearing transaction and that the granted authorization_details are reflected back in the token response.
Step 4 — Implement Dynamic Client Registration (RFC 7591) with a software_statement gate or Initial Access Token
Expose a /register endpoint and declare it in your RFC 8414 metadata as registration_endpoint so agents can self-register. Do not accept anonymous registration: per RFC 7591 §3.1.1, gate the endpoint with either a software_statement (a signed JWT vouching for the client) or an Initial Access Token issued out of band. Accept the registration POST with redirect_uris, client_name, and requested scope, return a client_id, and reject any registration request that fails the gate. This is the SHOULD-level recommendation in the MCP authorization spec — re-verify before launch.
Step 5 — Wire token revocation (RFC 7009) plus introspection (RFC 7662) for the user-facing "kill agent authority" button
Build the kill switch end to end. The user-facing button calls the RFC 7009 /revoke endpoint to invalidate the refresh_token and its associated access_tokens, then signals resource servers to drop any cached validity for that grant. Because revocation is not instantaneous when resource servers validate tokens locally, have high-value resource servers query the RFC 7662 introspection endpoint on each sensitive request instead of trusting cached JWT validity. Test that a revoked agent can no longer transact within your documented propagation window.
§15 · Continue the Guide
Next stops in the AgentMall guide.
The Window
The agent trust layer is being built right now.
Every quarter, the floor moves up. RAR support is shipping across Keycloak, Auth0, and Okta (re-verify before launch). DPoP is moving from acknowledged to expected for high-value flows. The MCP authorization spec is hardening its required baseline. The operators who specify their trust layer now — RFC 8414 metadata published, PKCE-only authorization code locked down, RAR spending limits enforced at the AS, and a real RFC 7009 + RFC 7662 kill switch — are the ones agents will trust with delegated purchase authority. The ones who stop at "use OAuth 2.0" will spend the back half of 2026 explaining to users why the spending cap leaked or the revoke button did not work.
Open the AgentMall Roadmap →