Skip to content

Authorization URL validations required by the OAuth 2.1 specification not implemented #3856

Open

Description

@padraig-ohiceadha

Initial Checks

Release line

2.x (current stable)

Description

Similar to the existing handling of resource urls OAuth 2.1 requires that URL fragments not be included in the authorization url and that it not contain any of the params used by the authorization request. Neither of these are currently checked for in _perform_authorization_code_grant
The specification also requires that any existing query params are retained, which are not today ( there is an issue open of this particular part of the issue - #2776 ).

For the OAuth 2.1 specification requirements see section 3.1 of https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1#name-authorization-endpoint

Example Code

Python & MCP Python SDK

Python 3.11.9
2.3.1.dev20

Activity

  1. added
    bugSomething isn't working
    v1Affects the v1.x maintenance line
    v2Affects the v2 line (2.x on main)
    on Nov 13, 2026
  2. media-ctrl commented on Nov 14, 2026

    @media-ctrl

    Confirmed on main (77001c9), and the failure mode is slightly worse than "not checked": neither check is present, so a query string is not merely dropped, it corrupts the request.

    Line 428 builds the URL as f"{auth_endpoint}?{urlencode(auth_params)}", an unconditional question mark. With authorization_endpoint: https://as.example.com/authorize?tenant=acme the result parses as:

    tenant=['acme?response_type=code'], client_id=['abc'], state=['s']
    

    So the first authorization parameter is swallowed into the value of the last existing one, and response_type never reaches the authorization server at all. It fails as invalid_request on a compliant server, and any server that defaults response_type to code proceeds with a mangled tenant value.

    The fragment case is quieter. https://as.example.com/authorize#frag plus the same parameters yields an empty query and frag?response_type=code&client_id=abc&state=s as the fragment, so the browser sends a request with no parameters and nothing on the client raises.

    Why the collision rule matters more here than it looks: should_include_resource_param adds resource whenever protected resource metadata is present or the negotiated version is 2025-06-18 or later, so an endpoint that advertises ?resource= or ?scope= is the case that collides in practice. OAuth 2.1 section 3.1 is explicit on both halves:

    "The authorization endpoint URL MUST NOT include a fragment component, and MAY include a query string component, which MUST be retained when adding additional query parameters. The query string component MUST NOT include any parameters defined for use in the authorization request."

    An endpoint that violates the third sentence is a metadata document the client must reject. RFC 8707 section 2 also requires the resource value to be an absolute URI and the authorization server applies audience policy on it, so a resource value truncated at the first existing parameter is an audience identifier problem, not a cosmetic URL one.

    On placement: validating inside _perform_authorization_code_grant fixes one redirect while leaving the bad metadata in context for the next refresh and the token request. Validating where the authorization server metadata is accepted means a bad document is rejected once, with one error, and the grant stays readable. There is already a precedent for the shape, since validate_resource_url is an injectable callback with a default implementation.

    The reassembly itself is small: parse the endpoint, keep its query parameters, reject if any name is in the set the grant is about to add, then rebuild the URL with the merged query instead of interpolating a question mark.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingv1Affects the v1.x maintenance linev2Affects the v2 line (2.x on main)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions