Summary
Complete RFC 9728 support by emitting the resource_metadata parameter in the WWW-Authenticate header that ZeroID returns on 401 from bearer-protected endpoints.
The cold-start UX without this: a stock OAuth client hits a 401, has no signpost pointing at the PRM document, and has to know the well-known URL out-of-band. With it, the client follows resource_metadata="…" → PRM → AS metadata per spec.
What needs to change
Every 401 ZeroID returns from a bearer-protected endpoint includes a WWW-Authenticate value of this shape:
WWW-Authenticate: Bearer error="invalid_token",
error_description="…",
resource_metadata="{Issuer}/.well-known/oauth-protected-resource"
The error and error_description parameters stay RFC 6750 §3.1-correct — invalid_request / invalid_token / insufficient_scope — keyed off whatever the middleware already determined. The PR's only job is to append resource_metadata to every 401's WWW-Authenticate.
Estimated scope
internal/handler/routes.go — helper on the API struct (uses a.issuer after PR-163 merges):
func (a *API) prmURL() string {
return a.issuer + "/.well-known/oauth-protected-resource"
}
internal/middleware/auth*.go — central bearer-auth middleware emits a 401 with the appended parameter. Most of the work centralizes here.
- Direct 401 emission sites — grep
http.StatusUnauthorized in handlers/services for sites that bypass the middleware (DPoP failures, JWT signature/expiry, introspection-side rejections). Each gets the same parameter.
- Flip the existing pin test —
TestRFC9728_S5_1_WWWAuthenticateResourceMetadataNotYetEmitted in tests/integration/prm_compliance_test.go is currently a negative-pin asserting NotContains "resource_metadata=". Rename to drop NotYetEmitted, flip to assert.Contains + URL match.
- Add a positive-shape compliance test — parse the
WWW-Authenticate value, confirm resource_metadata URL equals {Issuer}/.well-known/oauth-protected-resource, confirm RFC 6750 error field still well-formed.
- README standards-table — RFC 9728 row description tightens (e.g. add "+ §5.1 breadcrumb").
Verification at merge time
- Existing
TestRFC9728_S5_1_* test now expects the breadcrumb (flipped assertion).
- New positive-shape test parses the
WWW-Authenticate value end-to-end.
- Manual curl against a protected endpoint with a bogus token returns 401 with both
error="invalid_token" and resource_metadata="…" parameters; the URL resolves to a real PRM document.
References
Summary
Complete RFC 9728 support by emitting the
resource_metadataparameter in theWWW-Authenticateheader that ZeroID returns on 401 from bearer-protected endpoints.The cold-start UX without this: a stock OAuth client hits a 401, has no signpost pointing at the PRM document, and has to know the well-known URL out-of-band. With it, the client follows
resource_metadata="…"→ PRM → AS metadata per spec.What needs to change
Every 401 ZeroID returns from a bearer-protected endpoint includes a
WWW-Authenticatevalue of this shape:The
erroranderror_descriptionparameters stay RFC 6750 §3.1-correct —invalid_request/invalid_token/insufficient_scope— keyed off whatever the middleware already determined. The PR's only job is to appendresource_metadatato every 401'sWWW-Authenticate.Estimated scope
internal/handler/routes.go— helper on theAPIstruct (usesa.issuerafter PR-163 merges):internal/middleware/auth*.go— central bearer-auth middleware emits a 401 with the appended parameter. Most of the work centralizes here.http.StatusUnauthorizedin handlers/services for sites that bypass the middleware (DPoP failures, JWT signature/expiry, introspection-side rejections). Each gets the same parameter.TestRFC9728_S5_1_WWWAuthenticateResourceMetadataNotYetEmittedintests/integration/prm_compliance_test.gois currently a negative-pin assertingNotContains "resource_metadata=". Rename to dropNotYetEmitted, flip toassert.Contains+ URL match.WWW-Authenticatevalue, confirmresource_metadataURL equals{Issuer}/.well-known/oauth-protected-resource, confirm RFC 6750errorfield still well-formed.Verification at merge time
TestRFC9728_S5_1_*test now expects the breadcrumb (flipped assertion).WWW-Authenticatevalue end-to-end.error="invalid_token"andresource_metadata="…"parameters; the URL resolves to a real PRM document.References