OAuth Protected Resource: where an agent goes to get a token
What OAuth Protected Resource Metadata (RFC 9728) is, why agents need it, a minimal example, right vs wrong, mistakes, and how to verify.
Updated:
What it is
OAuth Protected Resource Metadata (RFC 9728) is JSON at
/.well-known/oauth-protected-resource where a protected resource (your API)
declares: which authorization servers issue tokens for it, which scopes are
needed, and how to present the token. It’s the counterpart to OAuth Discovery:
discovery describes the authorization server, this describes the protected
resource.
Why it matters for AI agents
An agent hits your API and gets a 401. Now what? Without metadata it doesn’t
know where to get a token. RFC 9728 answers: here are the
authorization_servers, go there. Discovery + protected-resource together let an
agent run the whole OAuth flow autonomously. This is exactly the scheme modern MCP
uses for authorization.
Minimal working example
GET /.well-known/oauth-protected-resource HTTP/1.1
{
"resource": "https://api.example.com",
"authorization_servers": ["https://example.com"],
"scopes_supported": ["read", "write"],
"bearer_methods_supported": ["header"]
}
Plus, on a 401, the resource points to where its metadata lives:
WWW-Authenticate: Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"
Right vs wrong
| Right | Wrong |
|---|---|
resource = the API’s canonical URL | A mismatch with the real address |
authorization_servers point to a valid RFC 8414 server | A link to a server with no discovery |
401 + WWW-Authenticate with resource_metadata | A 401 with no hint where the metadata is |
| Absolute HTTPS URLs | Relative/HTTP |
Common mistakes
authorization_serverspoints to a server with no OAuth Discovery — the chain breaks.- No
WWW-Authenticatewithresource_metadataon a401— the agent can’t find the entry point. resource≠ the actual URL — the client rejects the token (audience mismatch).- Declared scopes the server doesn’t issue.
How to verify
A scan checks for valid metadata. Manually:
curl -s https://api.example.com/.well-known/oauth-protected-resource | jq .
curl -sI https://api.example.com/ | grep -i www-authenticate
Expect valid JSON with resource and authorization_servers.