auth: issuer mix-up mitigation (#859)

This PR functions as a Reference implementation of
[SEP-2468](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2468)
/ [RFC9207](https://datatracker.ietf.org/doc/rfc9207/).

This PR hardens the MCP OAuth Client functionality against Mix-Up
attacks:
>   Mix-up attacks aim to steal an authorization code or access token by
>   tricking the client into sending the authorization code or access
> token to the attacker instead of the honest authorization or resource
>   server

This PR hardens the client by adding support for a new `iss` parameter
in authorization responses:
- Authorization Servers broadcast support for the `iss` parameter via
the `authorization_response_iss_parameter_supported` metadata parameter
- If the parameter is supported, clients expect to receive the `iss`
parameter in the authorization response
- Clients compare the `iss` parameter in the authorization response to
the `Issuer` parameter in the authorization metadata. The two must match
exactly for the response to be processed.

Fixes #941
This commit is contained in:
Max Gerber 2026-05-27 02:39:08 -07:00 • committed by GitHub
commit 2d47cc9664
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 151 additions and 9 deletions

View file

@ -247,6 +247,7 @@ This handler supports:
- [Client ID Metadata Documents](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#client-id-metadata-documents)
- [Pre-registered clients](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#preregistration)
- [Dynamic Client Registration](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#dynamic-client-registration)
- [RFC 9207](https://www.rfc-editor.org/rfc/rfc9207) Authorization Server Issuer Identification
To use it, configure the handler and assign it to the transport:
@ -258,11 +259,12 @@ authHandler, _ := auth.NewAuthorizationCodeHandler(&auth.AuthorizationCodeHandle
// PreregisteredClientConfig: ...
// DynamicClientRegistrationConfig: ...
AuthorizationCodeFetcher: func(ctx context.Context, args *auth.AuthorizationArgs) (*auth.AuthorizationResult, error) {
// Open the args.URL in a browser and return the resulting code and state.
// Open the args.URL in a browser and return the resulting code, state, and iss.
// See full example in examples/auth/client/main.go.
code := ...
state := ...
return &auth.AuthorizationResult{Code: code, State: state}, nil
iss := ... // "iss" query parameter from the redirect URI (RFC 9207)
return &auth.AuthorizationResult{Code: code, State: state, Iss: iss}, nil
},
})
@ -415,6 +417,22 @@ sets `UserID` on the returned `TokenInfo`, the streamable transport will:
`TokenInfo.UserID` to enable this protection. This prevents an attacker with a valid
token from hijacking another user's session by guessing or obtaining their session ID.
### Issuer Mix-Up
The [mitigation](https://www.rfc-editor.org/rfc/rfc9207) against issuer mix-up attacks is
implemented per [RFC 9207](https://www.rfc-editor.org/rfc/rfc9207). The SDK client validates
the `iss` parameter in authorization responses to ensure they originated from the expected
authorization server:
- If `iss` is present in the redirect URI, the SDK verifies it matches the issuer from the
authorization server's metadata. A mismatch results in an error.
- If `iss` is absent but the authorization server advertises
`authorization_response_iss_parameter_supported: true` in its [RFC 8414](https://www.rfc-editor.org/rfc/rfc8414)
metadata, the SDK rejects the response with an error.
The `AuthorizationCodeFetcher` is responsible for extracting the `iss` query parameter from
the redirect URI and returning it in [`AuthorizationResult.Iss`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/auth#AuthorizationResult).
## Utilities
### Cancellation

View file

@ -15,6 +15,7 @@ import (
"maps"
"net/http"
"net/http/httptest"
"net/url"
"slices"
"testing"
@ -178,6 +179,8 @@ func (s *FakeAuthorizationServer) handleMetadata(w http.ResponseWriter, r *http.
CodeChallengeMethodsSupported: []string{"S256"},
ClientIDMetadataDocumentSupported: cimdSupported,
TokenEndpointAuthMethodsSupported: []string{"client_secret_post", "client_secret_basic"},
// Advertise RFC 9207 support: the authorize endpoint includes "iss" in responses.
AuthorizationResponseIssParameterSupported: true,
}
// Set CORS headers for cross-origin client discovery.
w.Header().Set("Access-Control-Allow-Origin", "*")
@ -267,8 +270,9 @@ func (s *FakeAuthorizationServer) handleAuthorize(w http.ResponseWriter, r *http
}
state := r.URL.Query().Get("state")
issuer := s.URL() + s.config.IssuerPath
redirectURL := fmt.Sprintf("%s?code=%s&state=%s", redirectURI, code, state)
redirectURL := fmt.Sprintf("%s?code=%s&state=%s&iss=%s", redirectURI, code, state, url.QueryEscape(issuer))
http.Redirect(w, r, redirectURL, http.StatusFound)
}