auth: validate unadvertised RFC 9207 iss instead of rejecting it (#1187)

## Summary

Fixes #1152.

Sentry, Linear, and Notion omit
`authorization_response_iss_parameter_supported` from their
authorization-server metadata. Sentry still sends a correct `iss` in the
authorization response, so sign-in fails after the browser step: the
user has already consented and an authorization code has been issued,
but `validateIssuerResponse` rejects the exchange solely because the
server did not advertise RFC 9207 support.

RFC 9207 §2.4 says clients MUST extract and compare `iss` when the
parameter is present. §2.4 also notes that clients SHOULD discard an
unadvertised `iss`, but that is a recommendation and local policy may
accept servers that send `iss` without advertising. Validating a
matching `iss` is interoperable with those hosted MCP servers and still
catches mix-up attacks: a wrong `iss` is rejected whether or not support
was advertised.

When `iss` is present, this change always compares it to the expected
issuer (simple string equality). A match is accepted even if the server
did not advertise support. A mismatch still returns `authorization
response issuer %q does not match expected issuer %q`. Absence of `iss`
remains an error only when the server advertised support.

## Test plan

- [x] `go test ./auth/ -run TestValidateIssuerResponse -count=1`
- [x] `go test ./auth/...`

New `TestValidateIssuerResponse` cases:

- `UnadvertisedIssCorrect`: `iss == expectedIssuer`, `issSupported ==
false` → no error
- `UnadvertisedIssWrong`: `iss == "https://attacker.example.com"`,
`issSupported == false` → error containing `does not match expected
issuer`

Existing advertised+match / advertised+mismatch / missing-iss cases are
unchanged.

---------

Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
This commit is contained in:
杨成锴 2026-08-25 17:26:17 +08:00 • committed by GitHub
commit 32404652e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -97,6 +97,11 @@ type Config struct {
// IssueRefreshToken, if true, includes a refresh_token in token responses and
// enables grant_type=refresh_token at the /token endpoint.
IssueRefreshToken bool
// UnadvertiseIssParameter, if true, omits
// authorization_response_iss_parameter_supported from metadata while the
// authorize endpoint still includes iss. Used to test local policy for
// unadvertised RFC 9207 iss.
UnadvertiseIssParameter bool
}
// testRefreshToken is the refresh token issued and accepted by the fake server
@ -199,8 +204,9 @@ 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,
// Advertise RFC 9207 support unless UnadvertiseIssParameter is set:
// the authorize endpoint still includes "iss" in responses either way.
AuthorizationResponseIssParameterSupported: !s.config.UnadvertiseIssParameter,
}
// Set CORS headers for cross-origin client discovery.
w.Header().Set("Access-Control-Allow-Origin", "*")