In this article
May 8, 2025
May 8, 2025

Introducing RFC 9728: Say hello to standardized OAuth 2.0 resource metadata

OAuth 2.0 just got a major upgrade in how resources describe themselves — find out what RFC 9728 introduces and why it matters.

8 years and 8 months. That’s how long it took for the RFC 9728 to get published.

The first individual draft for what would become RFC 9728 landed in August 2016. It didn’t become an official RFC until April 2025. That’s longer than most startups live!

So, what exactly took nearly a decade to finalize?

Well, RFC 9728 is all about giving OAuth 2.0 resource servers (the APIs behind the scenes) a proper way to describe themselves using metadata. If you've worked with OAuth before, you know that clients and authorization servers have long had ways to advertise their capabilities (thanks to RFCs like 7591 and 8414). But protected resources? They were left out of the party—until now.

Let’s unpack what RFC 9728 finally brings to the table—and why it was worth the wait.

What is RFC 9728?

In short: it’s a new spec that lets resource servers (a.k.a. the APIs behind your apps) describe themselves via metadata, just like authorization servers already can.

Think of it like giving your API an online dating profile: it tells clients and auth servers what it supports, what scopes it likes, and how to talk to it securely.

Before RFC 9728, there was no standard way for clients or auth servers to discover the capabilities of a protected resource. You had to document everything, or hardcode stuff. Now there’s a metadata endpoint for that.

What’s in the metadata?

The metadata is a simple JSON document (optionally signed as a JWT) published at a .well-known URI, like this:

	
https://api.example.com/.well-known/oauth-protected-resource
	

Here’s what it might contain:

	
{
  "resource": "https://api.example.com/",
  "authorization_servers": ["https://auth.example.com/"],
  "bearer_methods_supported": ["header"],
  "jwks_uri": "https://api.example.com/keys.json",
  "scopes_supported": ["read", "write", "admin"]
}
	

Let’s break those down:

  • resource: Required—a URI that uniquely identifies this resource. Think of it as its name tag.
  • authorization_servers: Optional—lists which auth servers can issue access tokens for this resource.
  • bearer_methods_supported: Optional—lists the OAuth 2.0 bearer token presentation methods that this protected resource supports.
  • jwks_uri: Optional—where your resource’s public keys live (used to validate signed requests or tokens).
  • scopes_supported: Recommended—what scope strings your API accepts. Great for dynamic UIs or clients.

Other optional fields are allowed too, but these are the main ones.

Why did it take so long to publish?

RFC 9728 was a long time coming. First drafted way back in 2016, it didn’t actually get published until April 2025. That’s almost nine years! So what happened?

When RFC 9728 was first proposed, it didn’t get much attention. Its sibling spec, RFC 8414 (for Authorization Server Metadata), was more immediately useful and became widely adopted. But Protected Resource Metadata? People weren’t really asking for it yet, so it just kind of sat there.

Fast forward a few years and the world looked different. Microservices exploded, dynamic integrations became the norm, and AI agents started showing up in real workflows. Suddenly, having a standard way for clients to discover what an API supports — things like token formats or required scopes — made a lot more sense. The use case caught up with the spec.

Finally, like any IETF standard, RFC 9728 went through a lot of review and discussion. Building consensus, incorporating feedback, and making sure it would actually work for everyone takes time. A lot of time, in this case.

Why should devs care?

This spec adds a ton of quality-of-life improvements:

  • Easier integrations via metadata discovery: Instead of relying on out-of-band documentation or hardcoded assumptions, clients can now fetch metadata about a protected resource directly from a standardized .well-known/oauth-protected-resource endpoint. This includes information like supported token types (e.g., Bearer, DPoP), required scopes, and token introspection URLs. It’s the same concept as openid-configuration—but for APIs.
  • Better security: Metadata can be served over HTTPS and optionally signed as a JWT (application/oauth-protected-resource-jwt). This gives clients a way to verify the authenticity and integrity of the metadata they receive. It reduces the risk of misconfigurations (like pointing to the wrong authorization server) and defends against certain classes of spoofing or misrouting attacks.
  • Dynamic discovery: If you're building a client that talks to many different APIs—think API gateways, API developer tools, or federated service meshes—you no longer need to manually configure every endpoint’s scopes, issuer, or key sources. Clients can dynamically discover what each API expects and configure themselves accordingly. This enables plug-and-play support for new APIs without code changes.

How it works in practice

Let’s say your client app tries to access an API without knowing much about it. Instead of manually configuring everything up front, you can leverage the new .well-known metadata endpoint defined by RFC 9728 — and sometimes, you don’t even need to know that URL in advance.

Step 1: Hit the API

Your client makes a request to the resource:

	
GET /photos HTTP/1.1
Host: api.example.com
	

Step 2: Get redirected via WWW-Authenticate

If the resource is protected, the server responds with a 401 Unauthorized and includes a WWW-Authenticate header like this:

	
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer authorization_uri="https://auth.example.com",
    resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"
	

That resource_metadata value is your shortcut. It tells the client exactly where to go to learn how to interact with the resource.

Step 3: Fetch the metadata

Now your client fetches:

	
GET /.well-known/oauth-protected-resource HTTP/1.1
Host: api.example.com
Accept: application/oauth-protected-resource+json
	

And receives:

	
{
  "resource": "https://api.example.com/",
  "authorization_servers": ["https://auth.example.com/"],
  "bearer_methods_supported": ["header"],
  "jwks_uri": "https://api.example.com/keys.json",
  "scopes_supported": ["read", "write", "admin"]
}
	

Instead of plain JSON, the server can return a signed JWT containing the metadata. Here’s what the client might receive from the .well-known/oauth-protected-resource endpoint:

	
HTTP/1.1 200 OK
Content-Type: application/oauth-protected-resource-jwt

eyJhbGciOiJSUzI1NiIsImtpZCI6IjEyMzQ1NiJ9.eyJyZXNvdXJjZSI6Imh0dHBzOi8vYXBpLmV4YW1wbGUuY29tIiwiaXNzdWVyIjoiaHR0cHM6Ly9hdXRoLmV4YW1wbGUuY29tIiwidG9rZW5fZW5kcG9pbnQiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20vdG9rZW4iLCJzY29wZXNfc3VwcG9ydGVkIjpbInBob3Rvcy5yZWFkIiwicGhvdG9zLndyaXRlIl19.MEUCIQCb...
	

This JWT is signed using the authorization server’s private key. The client can:

  • Decode and verify it using the kid from the JWT header
  • Fetch the public key from the jwks_uri of the issuer

Step 4: Configure on the fly

The client now knows:

  • Where to request access tokens (token_endpoint)
  • Which scopes it needs
  • What auth methods are supported
  • Which issuer to trust

No out-of-band docs, no hardcoded values — it’s all self-discovering.

Best practices

A few things to keep in mind:

  • Always use HTTPS: This isn’t just a suggestion — it's a requirement. Metadata endpoints must be served over HTTPS to prevent tampering or man-in-the-middle attacks. Clients should reject any response served over plain HTTP.
  • Sign metadata whenever possible: Publishing metadata as a signed JWT (using application/oauth-protected-resource-jwt) adds a layer of trust. It allows clients to verify that the metadata came from a legitimate source and hasn’t been altered. Use keys from your authorization server’s JWKS URI, and rotate them responsibly.
  • Don’t blindly follow redirects: Clients should not automatically follow HTTP redirects when resolving metadata URLs. Redirects can be abused to trigger SSRF (Server-Side Request Forgery) or phishing. If you must support redirects, ensure strict validation of target URLs against a known allowlist.
  • Validate all metadata fields: Before acting on any metadata, clients should perform schema and value validation:
    • Ensure issuer matches expected patterns
    • Verify the token_endpoint uses HTTPS
    • Check scopes_supported for sanity (e.g., no unexpected wildcard scopes)
  • Cache intelligently: Metadata is unlikely to change frequently. Clients should cache it (respecting HTTP caching headers or setting their own expiry policy) to reduce load and improve startup time. Refresh periodically, not on every request.
  • Fail securely: If metadata discovery fails, fall back gracefully. Don’t skip validation or default to insecure behavior. Surface clear errors to the developer or user, and avoid making assumptions about scopes, issuers, or endpoints.
  • Monitor for changes: While the spec doesn't mandate versioning, you should monitor for unexpected changes in metadata fields — especially issuer, token_endpoint, or auth methods. These could indicate a misconfiguration or malicious alteration.

Final thoughts

RFC 9728 might seem like a small addition, but it ties together the OAuth 2.0 ecosystem in a really neat way. Clients, authorization servers, and resource servers can all speak the same discovery language now.

If you’re building APIs that rely on OAuth, consider exposing metadata as per RFC 9728. It’s simple, powerful, and saves everyone time.

This site uses cookies to improve your experience. Please accept the use of cookies on this site. You can review our cookie policy here and our privacy policy here. If you choose to refuse, functionality of this site will be limited.