go-sdk/oauthex/auth_meta.go
Rob Findley 307e32c2b3 oauthex: validate URL schemes in auth server metadata and DCR
Use URL scheme validation for remaining URL fields in auth
datastructures (AuthServerMeta and ClientRegistrationMetadata), to
prevent XSS attacks.

By comparison, the typescript SDK doesn't validate
introspection_endpoint, and I don't know why. Otherwise the
implementations align.

Fixes #526
2025-12-11 10:09:43 -05:00

187 lines
8 KiB
Go

// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
// This file implements Authorization Server Metadata.
// See https://www.rfc-editor.org/rfc/rfc8414.html.
//go:build mcp_go_client_oauth
package oauthex
import (
"context"
"errors"
"fmt"
"net/http"
)
// AuthServerMeta represents the metadata for an OAuth 2.0 authorization server,
// as defined in [RFC 8414].
//
// Not supported:
// - signed metadata
//
// Note: URL fields in this struct are validated by validateAuthServerMetaURLs to
// prevent XSS attacks. If you add a new URL field, you must also add it to that
// function.
//
// [RFC 8414]: https://tools.ietf.org/html/rfc8414)
type AuthServerMeta struct {
// GENERATED BY GEMINI 2.5.
// Issuer is the REQUIRED URL identifying the authorization server.
Issuer string `json:"issuer"`
// AuthorizationEndpoint is the REQUIRED URL of the server's OAuth 2.0 authorization endpoint.
AuthorizationEndpoint string `json:"authorization_endpoint"`
// TokenEndpoint is the REQUIRED URL of the server's OAuth 2.0 token endpoint.
TokenEndpoint string `json:"token_endpoint"`
// JWKSURI is the REQUIRED URL of the server's JSON Web Key Set [JWK] document.
JWKSURI string `json:"jwks_uri"`
// RegistrationEndpoint is the RECOMMENDED URL of the server's OAuth 2.0 Dynamic Client Registration endpoint.
RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
// ScopesSupported is a RECOMMENDED JSON array of strings containing a list of the OAuth 2.0
// "scope" values that this server supports.
ScopesSupported []string `json:"scopes_supported,omitempty"`
// ResponseTypesSupported is a REQUIRED JSON array of strings containing a list of the OAuth 2.0
// "response_type" values that this server supports.
ResponseTypesSupported []string `json:"response_types_supported"`
// ResponseModesSupported is a RECOMMENDED JSON array of strings containing a list of the OAuth 2.0
// "response_mode" values that this server supports.
ResponseModesSupported []string `json:"response_modes_supported,omitempty"`
// GrantTypesSupported is a RECOMMENDED JSON array of strings containing a list of the OAuth 2.0
// grant type values that this server supports.
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
// TokenEndpointAuthMethodsSupported is a RECOMMENDED JSON array of strings containing a list of
// client authentication methods supported by this token endpoint.
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported,omitempty"`
// TokenEndpointAuthSigningAlgValuesSupported is a RECOMMENDED JSON array of strings containing
// a list of the JWS signing algorithms ("alg" values) supported by the token endpoint for
// the signature on the JWT used to authenticate the client.
TokenEndpointAuthSigningAlgValuesSupported []string `json:"token_endpoint_auth_signing_alg_values_supported,omitempty"`
// ServiceDocumentation is a RECOMMENDED URL of a page containing human-readable documentation
// for the service.
ServiceDocumentation string `json:"service_documentation,omitempty"`
// UILocalesSupported is a RECOMMENDED JSON array of strings representing supported
// BCP47 [RFC5646] language tag values for display in the user interface.
UILocalesSupported []string `json:"ui_locales_supported,omitempty"`
// OpPolicyURI is a RECOMMENDED URL that the server provides to the person registering
// the client to read about the server's operator policies.
OpPolicyURI string `json:"op_policy_uri,omitempty"`
// OpTOSURI is a RECOMMENDED URL that the server provides to the person registering the
// client to read about the server's terms of service.
OpTOSURI string `json:"op_tos_uri,omitempty"`
// RevocationEndpoint is a RECOMMENDED URL of the server's OAuth 2.0 revocation endpoint.
RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
// RevocationEndpointAuthMethodsSupported is a RECOMMENDED JSON array of strings containing
// a list of client authentication methods supported by this revocation endpoint.
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
// RevocationEndpointAuthSigningAlgValuesSupported is a RECOMMENDED JSON array of strings
// containing a list of the JWS signing algorithms ("alg" values) supported by the revocation
// endpoint for the signature on the JWT used to authenticate the client.
RevocationEndpointAuthSigningAlgValuesSupported []string `json:"revocation_endpoint_auth_signing_alg_values_supported,omitempty"`
// IntrospectionEndpoint is a RECOMMENDED URL of the server's OAuth 2.0 introspection endpoint.
IntrospectionEndpoint string `json:"introspection_endpoint,omitempty"`
// IntrospectionEndpointAuthMethodsSupported is a RECOMMENDED JSON array of strings containing
// a list of client authentication methods supported by this introspection endpoint.
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"`
// IntrospectionEndpointAuthSigningAlgValuesSupported is a RECOMMENDED JSON array of strings
// containing a list of the JWS signing algorithms ("alg" values) supported by the introspection
// endpoint for the signature on the JWT used to authenticate the client.
IntrospectionEndpointAuthSigningAlgValuesSupported []string `json:"introspection_endpoint_auth_signing_alg_values_supported,omitempty"`
// CodeChallengeMethodsSupported is a RECOMMENDED JSON array of strings containing a list of
// PKCE code challenge methods supported by this authorization server.
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported,omitempty"`
}
var wellKnownPaths = []string{
"/.well-known/oauth-authorization-server",
"/.well-known/openid-configuration",
}
// GetAuthServerMeta issues a GET request to retrieve authorization server metadata
// from an OAuth authorization server with the given issuerURL.
//
// It follows [RFC 8414]:
// - The well-known paths specified there are inserted into the URL's path, one at time.
// The first to succeed is used.
// - The Issuer field is checked against issuerURL.
//
// [RFC 8414]: https://tools.ietf.org/html/rfc8414
func GetAuthServerMeta(ctx context.Context, issuerURL string, c *http.Client) (*AuthServerMeta, error) {
var errs []error
for _, p := range wellKnownPaths {
u, err := prependToPath(issuerURL, p)
if err != nil {
// issuerURL is bad; no point in continuing.
return nil, err
}
asm, err := getJSON[AuthServerMeta](ctx, c, u, 1<<20)
if err == nil {
if asm.Issuer != issuerURL { // section 3.3
// Security violation; don't keep trying.
return nil, fmt.Errorf("metadata issuer %q does not match issuer URL %q", asm.Issuer, issuerURL)
}
if len(asm.CodeChallengeMethodsSupported) == 0 {
return nil, fmt.Errorf("authorization server at %s does not implement PKCE", issuerURL)
}
// Validate endpoint URLs to prevent XSS attacks (see #526).
if err := validateAuthServerMetaURLs(asm); err != nil {
return nil, err
}
return asm, nil
}
errs = append(errs, err)
}
return nil, fmt.Errorf("failed to get auth server metadata from %q: %w", issuerURL, errors.Join(errs...))
}
// validateAuthServerMetaURLs validates all URL fields in AuthServerMeta
// to ensure they don't use dangerous schemes that could enable XSS attacks.
func validateAuthServerMetaURLs(asm *AuthServerMeta) error {
urls := []struct {
name string
value string
}{
{"authorization_endpoint", asm.AuthorizationEndpoint},
{"token_endpoint", asm.TokenEndpoint},
{"jwks_uri", asm.JWKSURI},
{"registration_endpoint", asm.RegistrationEndpoint},
{"service_documentation", asm.ServiceDocumentation},
{"op_policy_uri", asm.OpPolicyURI},
{"op_tos_uri", asm.OpTOSURI},
{"revocation_endpoint", asm.RevocationEndpoint},
{"introspection_endpoint", asm.IntrospectionEndpoint},
}
for _, u := range urls {
if err := checkURLScheme(u.value); err != nil {
return fmt.Errorf("%s: %w", u.name, err)
}
}
return nil
}