Package rfc9728
Overview
Package rfc9728 implements OAuth 2.0 Protected Resource Metadata, from RFC 9728: the JSON document a resource server publishes at a well-known URL so clients and authorization servers can discover its configuration, and the resource_metadata parameter that points a client to it from a 401.
The Model Context Protocol requires MCP servers to publish it, so clients can discover where to sign in:
resource := "https://mcp.example.com/mcp"
md := rfc9728.Metadata{
Resource: resource,
AuthorizationServers: []string{"https://as.example.com"},
ScopesSupported: []string{"read"},
}
metadataURL, err := rfc9728.WellKnownURL(resource)
param, err := rfc9728.MetadataParam(metadataURL)
bearer := rfc6750.MustChallenge("mcp").WithParam(param)The package doesn't cover signed metadata (Section 2.2).
The RFC text is in rfc9728.txt in this directory, and the doc comments cite its sections.
Index
Constants
const MetadataProtectedResources = "protected_resources"MetadataProtectedResources is the OAuth Authorization Server Metadata member that lists the protected resources the authorization server issues tokens for (Section 4). Set it on rfc8414's Metadata.WithExtension.
const ParamResourceMetadata rfc9110.Token = "resource_metadata"ParamResourceMetadata is the WWW-Authenticate auth-param that carries the metadata URL (Section 5.1).
const WellKnownSuffix = "oauth-protected-resource"WellKnownSuffix is the well-known URI suffix for protected resource metadata (Section 3).
Variables
var (
// ErrInvalidResource is returned by [WellKnownURL] for a resource
// identifier that isn't an absolute URL with a host.
ErrInvalidResource = errors.New("invalid resource identifier")
// ErrResourceMismatch is returned by [Metadata.CheckResource] when a
// metadata document's resource isn't the one it was fetched for.
ErrResourceMismatch = errors.New("resource mismatch")
// ErrAuthorizationServerMismatch is returned by
// [Metadata.CheckAuthorizationServer] when a metadata document lists
// authorization_servers that don't include the expected issuer.
ErrAuthorizationServerMismatch = errors.New("authorization server mismatch")
)Functions
MetadataParam source
func MetadataParam(metadataURL string) (rfc9110.AuthParam, error)MetadataParam returns the resource_metadata auth-param for a WWW-Authenticate challenge, pointing at the metadata URL (Section 5.1). Add it to a Bearer challenge with rfc6750's Challenge.WithParam.
WellKnownURL source
func WellKnownURL(resource string) (string, error)WellKnownURL returns the URL of the metadata document for the resource: the well-known path inserted between the host and any path, with the path's trailing "/" removed, and any query kept (Section 3.1). It returns ErrInvalidResource if the resource isn't an absolute URL with a host, or has a fragment, which Section 1.2 forbids.
Types
Metadata source
type Metadata struct {
Resource string `json:"resource"`
AuthorizationServers []string `json:"authorization_servers,omitempty"`
JWKSURI string `json:"jwks_uri,omitempty"`
ScopesSupported []string `json:"scopes_supported,omitempty"`
BearerMethodsSupported []string `json:"bearer_methods_supported,omitempty"`
ResourceSigningAlgValuesSupported []string `json:"resource_signing_alg_values_supported,omitempty"`
ResourceName string `json:"resource_name,omitempty"`
ResourceDocumentation string `json:"resource_documentation,omitempty"`
ResourcePolicyURI string `json:"resource_policy_uri,omitempty"`
ResourceTOSURI string `json:"resource_tos_uri,omitempty"`
TLSClientCertificateBoundAccessTokens bool `json:"tls_client_certificate_bound_access_tokens,omitempty"`
AuthorizationDetailsTypesSupported []string `json:"authorization_details_types_supported,omitempty"`
DPoPSigningAlgValuesSupported []string `json:"dpop_signing_alg_values_supported,omitempty"`
DPoPBoundAccessTokensRequired bool `json:"dpop_bound_access_tokens_required,omitempty"`
// contains filtered or unexported fields
}Metadata is a protected resource metadata document, with the members Section 2 defines and any extension members. Set the standard members directly, and extension members with Metadata.WithExtension.
Metadata has its own MarshalJSON and UnmarshalJSON, so extension members are kept by both encoding/json and encoding/json/v2.
See RFC 9728, Section 2.
(Metadata) CheckAuthorizationServer source
func (m Metadata) CheckAuthorizationServer(issuer string) errorCheckAuthorizationServer returns ErrAuthorizationServerMismatch if m lists authorization_servers and issuer isn't among them, compared code point by code point (Sections 4, 6 and 7.6). If m's authorization_servers is empty, it returns nil.
(Metadata) CheckResource source
func (m Metadata) CheckResource(resource string) errorCheckResource returns ErrResourceMismatch unless m's resource is identical to the resource the client built the metadata URL from. Section 3.3 says a client MUST check this, so an attacker can't publish metadata that impersonates another resource.
(Metadata) Extension source
func (m Metadata) Extension(name string, v any) (bool, error)Extension unmarshals the extension member with the name into v, as json.Unmarshal does. It reports false if m has no such member.
(Metadata) MarshalJSON source
func (m Metadata) MarshalJSON() ([]byte, error)MarshalJSON returns m as a JSON object: the standard members, leaving out empty optional ones, then extension members sorted by name.
(Metadata) UnmarshalJSON source
func (m *Metadata) UnmarshalJSON(data []byte) errorUnmarshalJSON reads a metadata document, keeping members it doesn't define as extension members.
(Metadata) WithExtension source
func (m Metadata) WithExtension(name string, value any) MetadataWithExtension returns a copy of m with an extension member, replacing any with the same name. The value is marshalled when m is, so Metadata.MarshalJSON returns an error if it can't be, or if the name is a standard member's.