diff --git a/public/og-default.png b/public/og-default.png index 88ec504a..120c1139 100644 Binary files a/public/og-default.png and b/public/og-default.png differ diff --git a/public/og/checklist.png b/public/og/checklist.png index 20457ef7..13f11cf4 100644 Binary files a/public/og/checklist.png and b/public/og/checklist.png differ diff --git a/public/og/spec.png b/public/og/spec.png index 476638ad..23cb9f93 100644 Binary files a/public/og/spec.png and b/public/og/spec.png differ diff --git a/public/og/spec/well-known.png b/public/og/spec/well-known.png index c0b5faf4..1a59cc5c 100644 Binary files a/public/og/spec/well-known.png and b/public/og/spec/well-known.png differ diff --git a/public/og/spec/well-known/oauth-authorization-server.png b/public/og/spec/well-known/oauth-authorization-server.png new file mode 100644 index 00000000..c0e371a2 Binary files /dev/null and b/public/og/spec/well-known/oauth-authorization-server.png differ diff --git a/public/og/spec/well-known/oauth-protected-resource.png b/public/og/spec/well-known/oauth-protected-resource.png new file mode 100644 index 00000000..7d411216 Binary files /dev/null and b/public/og/spec/well-known/oauth-protected-resource.png differ diff --git a/src/content/changelog/2026-07-08-oauth-protected-resource.md b/src/content/changelog/2026-07-08-oauth-protected-resource.md new file mode 100644 index 00000000..5c623d78 --- /dev/null +++ b/src/content/changelog/2026-07-08-oauth-protected-resource.md @@ -0,0 +1,8 @@ +--- +title: Added pages on OAuth 2.0 discovery metadata +date: "2026-07-08" +type: added +relatedSlugs: [oauth-protected-resource, oauth-authorization-server] +--- + +Added two pages completing the OAuth 2.0 discovery set: [`/.well-known/oauth-protected-resource`](/spec/well-known/oauth-protected-resource/) (RFC 9728), the metadata document naming the authorisation server(s) that issue tokens for a protected resource — now required of authenticated MCP servers — and [`/.well-known/oauth-authorization-server`](/spec/well-known/oauth-authorization-server/) (RFC 8414), the plain-OAuth sibling of [`openid-configuration`](/spec/well-known/openid-configuration/) for authorisation servers with no OpenID Connect layer. Both are `optional`: they apply only if you expose an OAuth-protected resource or run an authorisation server. diff --git a/src/content/spec/agent-readiness/mcp-and-tool-discovery.md b/src/content/spec/agent-readiness/mcp-and-tool-discovery.md index 7e8ddd73..ff726d81 100644 --- a/src/content/spec/agent-readiness/mcp-and-tool-discovery.md +++ b/src/content/spec/agent-readiness/mcp-and-tool-discovery.md @@ -6,7 +6,7 @@ summary: "The Model Context Protocol is an emerging way for sites to expose quer status: optional order: 80 appliesTo: [all] -relatedSlugs: [agent-readiness-overview, machine-readable-formats, llms-txt, link-headers, api-catalog, agent-skills-discovery, a2a-agent-cards, webmcp, nlweb, agentic-resource-discovery] +relatedSlugs: [agent-readiness-overview, machine-readable-formats, llms-txt, link-headers, api-catalog, agent-skills-discovery, a2a-agent-cards, webmcp, nlweb, agentic-resource-discovery, oauth-protected-resource] updated: "2026-06-17T00:00:00.000Z" sources: - title: "Model Context Protocol" diff --git a/src/content/spec/well-known/oauth-authorization-server.md b/src/content/spec/well-known/oauth-authorization-server.md new file mode 100644 index 00000000..c964b0ad --- /dev/null +++ b/src/content/spec/well-known/oauth-authorization-server.md @@ -0,0 +1,73 @@ +--- +title: "/.well-known/oauth-authorization-server" +slug: oauth-authorization-server +category: well-known +summary: "A JSON metadata document describing a plain OAuth 2.0 authorisation server's endpoints and capabilities. The non-OIDC sibling of openid-configuration; only needed if you run an authorisation server." +status: optional +order: 31 +appliesTo: [all] +relatedSlugs: [openid-configuration, oauth-protected-resource, well-known-overview] +updated: "2026-07-08T00:00:00.000Z" +sources: + - title: "RFC 8414 — OAuth 2.0 Authorization Server Metadata" + url: "https://www.rfc-editor.org/rfc/rfc8414" + publisher: "IETF" + - title: "RFC 9728 — OAuth 2.0 Protected Resource Metadata" + url: "https://www.rfc-editor.org/rfc/rfc9728" + publisher: "IETF" + - title: "OpenID Connect Discovery 1.0" + url: "https://openid.net/specs/openid-connect-discovery-1_0.html" + publisher: "OpenID Foundation" + - title: "IANA — Well-Known URIs Registry" + url: "https://www.iana.org/assignments/well-known-uris/well-known-uris.xhtml" + publisher: "IANA" +--- + +## What it is + +`/.well-known/oauth-authorization-server` is a JSON metadata document, defined by **RFC 8414**, that an OAuth 2.0 **authorisation server** publishes to describe itself: where to send authorisation requests, where to exchange tokens, which scopes and grant types it supports, and where its signing keys live (`jwks_uri`). + +It is the plain-OAuth sibling of [`/.well-known/openid-configuration`](/spec/well-known/openid-configuration/). The two carry almost the same fields — OpenID Connect Discovery came first, and RFC 8414 was modelled on it — but they describe different servers. Publish `openid-configuration` if you are an **OpenID Connect** provider (you issue `id_token`s and expose a UserInfo endpoint); publish `oauth-authorization-server` if you are a **plain OAuth 2.0** authorisation server with no OIDC layer. A server that is both may publish both. + +## Why it matters + +- **Zero hand-configuration.** A client needs only your issuer URL and discovers every endpoint from this document. +- **It completes the discovery chain.** A protected resource's [`oauth-protected-resource`](/spec/well-known/oauth-protected-resource/) metadata (RFC 9728) lists its `authorization_servers`; the client fetches this document from each to find the token endpoint. Resource metadata says _who_ issues tokens; this says _how_ to get one. +- **Key rotation and capability negotiation.** `jwks_uri` is the canonical key source, and `scopes_supported` / `grant_types_supported` tell clients what you allow. + +Only publish it if you actually run an authorisation server. An application that merely _consumes_ OAuth — "sign in with someone else's provider" — is a client, not a server, and has nothing to advertise here. + +## How to implement + +Serve the document as `application/json` over HTTPS. A minimal payload: + +```json +{ + "issuer": "https://login.example.com", + "authorization_endpoint": "https://login.example.com/oauth2/authorize", + "token_endpoint": "https://login.example.com/oauth2/token", + "jwks_uri": "https://login.example.com/oauth2/jwks", + "response_types_supported": ["code"], + "grant_types_supported": ["authorization_code", "refresh_token"], + "scopes_supported": ["read", "write"] +} +``` + +The one rule that trips people up: for an issuer that has a **path component**, RFC 8414 _inserts_ the well-known segment before the path — issuer `https://example.com/tenant1` → `https://example.com/.well-known/oauth-authorization-server/tenant1` — whereas OIDC _appends_ it (`https://example.com/tenant1/.well-known/openid-configuration`). Get this wrong and multi-tenant clients cannot find you. + +Otherwise the mechanics match [`openid-configuration`](/spec/well-known/openid-configuration/): keep `issuer` an exact match for the URL clients use, serve over HTTPS, return `200`, cache for minutes-to-hours, and let it through any authentication middleware. + +## Common mistakes + +- An `issuer` value that does not match the URL clients use — differing by a trailing slash, port, or hostname casing. The single most common "invalid issuer" cause. +- Using OIDC's _append_ rule instead of RFC 8414's _insert_ rule for path-bearing issuers. +- Publishing it when you only consume OAuth. That makes you a client, not a server. +- Serving it as `text/html` or behind authentication. + +## Verification + +``` +curl -s https://login.example.com/.well-known/oauth-authorization-server | jq . +``` + +The response should be valid JSON with at least `issuer`, `authorization_endpoint`, `token_endpoint`, and `jwks_uri`. The `issuer` value must equal the URL prefix you advertise to clients. diff --git a/src/content/spec/well-known/oauth-protected-resource.md b/src/content/spec/well-known/oauth-protected-resource.md new file mode 100644 index 00000000..45841099 --- /dev/null +++ b/src/content/spec/well-known/oauth-protected-resource.md @@ -0,0 +1,75 @@ +--- +title: "/.well-known/oauth-protected-resource" +slug: oauth-protected-resource +category: well-known +summary: "A JSON metadata document that tells clients which authorisation server issues the tokens an OAuth-protected API accepts, and how to present them. Only needed if you expose an OAuth-protected resource." +status: optional +order: 32 +appliesTo: [all] +relatedSlugs: [openid-configuration, oauth-authorization-server, well-known-overview, mcp-and-tool-discovery, webauthn] +updated: "2026-07-08T00:00:00.000Z" +sources: + - title: "RFC 9728 — OAuth 2.0 Protected Resource Metadata" + url: "https://www.rfc-editor.org/rfc/rfc9728" + publisher: "IETF" + - title: "RFC 8414 — OAuth 2.0 Authorization Server Metadata" + url: "https://www.rfc-editor.org/rfc/rfc8414" + publisher: "IETF" + - title: "IANA — Well-Known URIs Registry" + url: "https://www.iana.org/assignments/well-known-uris/well-known-uris.xhtml" + publisher: "IANA" + - title: "Model Context Protocol — Authorization" + url: "https://modelcontextprotocol.io/specification/draft/basic/authorization" + publisher: "Anthropic / MCP" +--- + +## What it is + +`/.well-known/oauth-protected-resource` is a JSON metadata document that an OAuth 2.0 **protected resource** — an API, or any endpoint that requires an access token — publishes so a client can discover, with no hand-configuration, which authorisation server issues the tokens it accepts and how to present them. It is defined by **RFC 9728**. + +It is the resource-server counterpart to the authorisation-server documents at `/.well-known/oauth-authorization-server` (RFC 8414) and [`/.well-known/openid-configuration`](/spec/well-known/openid-configuration/). Those describe where to _get_ a token; this describes what a specific resource _expects_. + +## Why it matters + +- **Zero hand-configuration.** A client that receives a `401` from a protected resource can learn, from this one document, exactly which authorisation server to send the user to. +- **The 401 handshake.** The resource returns `WWW-Authenticate: Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"`, so even a client that did not know the path is pointed straight at it. +- **Many issuers, one resource.** `authorization_servers` can list several trusted issuers; the client picks the right one instead of guessing. +- **Agent readiness.** The Model Context Protocol's authorization spec **requires** a remote (authenticated) MCP server to publish this document, and MCP clients use it to bootstrap OAuth. See [MCP and tool discovery](/spec/agent-readiness/mcp-and-tool-discovery/). + +Only publish this if you actually expose an OAuth-protected resource. A public, unauthenticated API or MCP endpoint has nothing to advertise here — like `openid-configuration`, it is `optional` and applies only when the condition holds. (This site's own MCP endpoint is public and unauthenticated, so it does not serve this file.) + +## How to implement + +Serve the document as `application/json` from the resource's origin. A minimal payload: + +```json +{ + "resource": "https://api.example.com", + "authorization_servers": ["https://login.example.com"], + "scopes_supported": ["read", "write"], + "bearer_methods_supported": ["header"], + "jwks_uri": "https://api.example.com/.well-known/jwks.json" +} +``` + +Rules: + +- The `resource` value must **exactly match** the resource identifier that appears in the `aud` (audience) claim of issued tokens, scheme and all. +- List every issuer you accept in `authorization_servers`. The client fetches each one's own metadata (RFC 8414 / OIDC) to find the token endpoint. +- On a `401`, set `WWW-Authenticate` with a `resource_metadata` parameter pointing at this URL, so discovery works without out-of-band configuration. +- Serve over **HTTPS**, return **200**, and let it through any authentication middleware — a discovery document hidden behind the very login it describes is useless. + +## Common mistakes + +- A `resource` value that does not match the `aud` claim in issued tokens — clients reject the mismatch. +- Omitting the `WWW-Authenticate: … resource_metadata=…` pointer, so only clients that already know the path can find it. +- Confusing this (the **resource server**) with `oauth-authorization-server` / `openid-configuration` (the **authorisation server**). They describe different roles. +- Serving the file behind authentication, or as `text/html`. + +## Verification + +``` +curl -s https://api.example.com/.well-known/oauth-protected-resource | jq . +``` + +The response should be valid JSON with at least `resource` and `authorization_servers`. Trigger a `401` from the resource and confirm the `WWW-Authenticate` header carries a `resource_metadata` link back to this document. diff --git a/src/content/spec/well-known/openid-configuration.md b/src/content/spec/well-known/openid-configuration.md index d692be26..317a9483 100644 --- a/src/content/spec/well-known/openid-configuration.md +++ b/src/content/spec/well-known/openid-configuration.md @@ -6,7 +6,7 @@ summary: "A JSON discovery document that describes an OpenID Connect provider's status: optional order: 30 appliesTo: [all] -relatedSlugs: [well-known-overview] +relatedSlugs: [well-known-overview, oauth-authorization-server, oauth-protected-resource] updated: "2026-05-29T09:13:20.000Z" sources: - title: "OpenID Connect Discovery 1.0" diff --git a/src/content/spec/well-known/well-known-overview.md b/src/content/spec/well-known/well-known-overview.md index a01da0ab..9a31d974 100644 --- a/src/content/spec/well-known/well-known-overview.md +++ b/src/content/spec/well-known/well-known-overview.md @@ -6,7 +6,7 @@ summary: "The /.well-known/ path prefix is a standardised place to publish site- status: recommended order: 10 appliesTo: [all] -relatedSlugs: [change-password, openid-configuration, webfinger, apple-app-site-association, assetlinks-json, nodeinfo, api-catalog, webauthn] +relatedSlugs: [change-password, openid-configuration, oauth-authorization-server, oauth-protected-resource, webfinger, apple-app-site-association, assetlinks-json, nodeinfo, api-catalog, webauthn] updated: "2026-06-08T12:00:00.000Z" sources: - title: "RFC 8615 — Well-Known Uniform Resource Identifiers (URIs)" @@ -38,6 +38,8 @@ The names allowed under `/.well-known/` are not arbitrary. IANA maintains a publ - **Interoperability.** Standardised paths are what let Mastodon, Apple, Google, OpenID Connect, ACME (Let's Encrypt) and others work across millions of sites without coordination. - **Stability.** The reserved prefix keeps protocol metadata out of the way of your application's routing. You will not accidentally collide with `/security.txt` if it lives under `/.well-known/`. +Some capabilities span **several** well-known URIs that reference each other, so a client can follow the chain with no prior configuration. OAuth 2.0 discovery is the clearest example: a protected resource describes itself at [`/.well-known/oauth-protected-resource`](/spec/well-known/oauth-protected-resource/) and names its authorisation server(s), whose own metadata lives at [`/.well-known/oauth-authorization-server`](/spec/well-known/oauth-authorization-server/) — or [`/.well-known/openid-configuration`](/spec/well-known/openid-configuration/) for OpenID Connect. + ## How to implement - Serve the resource over **HTTPS** on the canonical host.