auth: allow passing custom http.Client to AuthorizationCodeHandler (#840)

This commit is contained in:
Maciej Kisiel 2026-03-13 10:06:58 +01:00 • committed by GitHub
commit d836ceb87d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 173 additions and 51 deletions

View file

@ -14,9 +14,6 @@ import (
"errors"
"fmt"
"net/http"
"net/url"
"github.com/modelcontextprotocol/go-sdk/internal/util"
)
// AuthServerMeta represents the metadata for an OAuth 2.0 authorization server,
@ -136,13 +133,9 @@ type AuthServerMeta struct {
//
// [RFC 8414]: https://tools.ietf.org/html/rfc8414
func GetAuthServerMeta(ctx context.Context, metadataURL, issuer string, c *http.Client) (*AuthServerMeta, error) {
u, err := url.Parse(metadataURL)
if err != nil {
return nil, err
}
// Only allow HTTP for local addresses (testing or development purposes).
if !util.IsLoopback(u.Host) && u.Scheme != "https" {
return nil, fmt.Errorf("metadataURL %q does not use HTTPS", metadataURL)
if err := checkHTTPSOrLoopback(metadataURL); err != nil {
return nil, fmt.Errorf("metadataURL: %v", err)
}
asm, err := getJSON[AuthServerMeta](ctx, c, metadataURL, 1<<20)
if err != nil {
@ -173,6 +166,8 @@ func GetAuthServerMeta(ctx context.Context, metadataURL, issuer string, c *http.
// validateAuthServerMetaURLs validates all URL fields in AuthServerMeta
// to ensure they don't use dangerous schemes that could enable XSS attacks.
// It also validates that URLs likely to be called by the client use
// HTTPS or are loopback addresses.
func validateAuthServerMetaURLs(asm *AuthServerMeta) error {
urls := []struct {
name string
@ -194,5 +189,22 @@ func validateAuthServerMetaURLs(asm *AuthServerMeta) error {
return fmt.Errorf("%s: %w", u.name, err)
}
}
urls = []struct {
name string
value string
}{
{"authorization_endpoint", asm.AuthorizationEndpoint},
{"token_endpoint", asm.TokenEndpoint},
{"registration_endpoint", asm.RegistrationEndpoint},
{"introspection_endpoint", asm.IntrospectionEndpoint},
}
for _, u := range urls {
if err := checkHTTPSOrLoopback(u.value); err != nil {
return fmt.Errorf("%s: %w", u.name, err)
}
}
return nil
}

View file

@ -17,6 +17,8 @@ import (
"net/http"
"net/url"
"strings"
"github.com/modelcontextprotocol/go-sdk/internal/util"
)
type httpStatusError struct {
@ -78,3 +80,17 @@ func checkURLScheme(u string) error {
}
return nil
}
func checkHTTPSOrLoopback(addr string) error {
if addr == "" {
return nil
}
u, err := url.Parse(addr)
if err != nil {
return err
}
if !util.IsLoopback(u.Host) && u.Scheme != "https" {
return fmt.Errorf("URL %q does not use HTTPS or is not a loopback address", addr)
}
return nil
}

View file

@ -98,13 +98,9 @@ func resourceMetadataURL(cs []Challenge) string {
// - The authorization_servers field of the resulting metadata is checked for dangerous URL schemes.
func GetProtectedResourceMetadata(ctx context.Context, metadataURL, resourceURL string, c *http.Client) (_ *ProtectedResourceMetadata, err error) {
defer util.Wrapf(&err, "GetProtectedResourceMetadata(%q)", metadataURL)
u, err := url.Parse(metadataURL)
if err != nil {
return nil, err
}
// Only allow HTTP for local addresses (testing or development purposes).
if !util.IsLoopback(u.Host) && u.Scheme != "https" {
return nil, fmt.Errorf("metadataURL %q does not use HTTPS", metadataURL)
if err := checkHTTPSOrLoopback(metadataURL); err != nil {
return nil, fmt.Errorf("metadataURL: %v", err)
}
prm, err := getJSON[ProtectedResourceMetadata](ctx, c, metadataURL, 1<<20)
if err != nil {
@ -115,9 +111,12 @@ func GetProtectedResourceMetadata(ctx context.Context, metadataURL, resourceURL
return nil, fmt.Errorf("got metadata resource %q, want %q", prm.Resource, resourceURL)
}
// Validate the authorization server URLs to prevent XSS attacks (see #526).
for _, u := range prm.AuthorizationServers {
for i, u := range prm.AuthorizationServers {
if err := checkURLScheme(u); err != nil {
return nil, err
return nil, fmt.Errorf("authorization_servers[%d]: %v", i, err)
}
if err := checkHTTPSOrLoopback(u); err != nil {
return nil, fmt.Errorf("authorization_servers[%d]: %v", i, err)
}
}
return prm, nil