Skip to content

OidcProviderConfigurationEndpointFilter (multi-issuer) only matches the path-append discovery location, not path-insertion — asymmetric with OAuth2AuthorizationServerMetadataEndpointFilter and breaks MCP discovery priority #2 #19342

@sandipchitale

Description

@sandipchitale

Describe the bug

When AuthorizationServerSettings.multipleIssuersAllowed(true) is set, the two metadata endpoint filters use opposite and incompatible well-known URL shapes for an issuer that has a path component, so only one of
the two OpenID Connect discovery locations defined for path-bearing issuers is served.

OAuth2AuthorizationServerMetadataEndpointFilter matches the path-insertion shape /.well-known/oauth-authorization-server/** (well-known segment first, issuer path appended):

https://github.com/spring-projects/spring-security/blob/43f958e0587e07bcb322194ce2acf9ad921a5b88/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2A
uthorizationServerMetadataEndpointFilter.java#L131

OidcProviderConfigurationEndpointFilter matches only the path-append shape /**/.well-known/openid-configuration (issuer path first, well-known segment last):

https://github.com/spring-projects/spring-security/blob/43f958e0587e07bcb322194ce2acf9ad921a5b88/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/Oi
dcProviderConfigurationEndpointFilter.java#L136

As a result, for an issuer such as https://host/tenant1:

This asymmetry breaks interoperability with the Model Context Protocol (MCP) authorization spec, which requires clients to try the OIDC path-insertion location and lists it ahead of the append location. From
https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization (section "Authorization Server Metadata Discovery"):

"For issuer URLs with path components (e.g., https://auth.example.com/tenant1), clients MUST try endpoints in the following priority order:

  1. OAuth 2.0 Authorization Server Metadata with path insertion: https://auth.example.com/.well-known/oauth-authorization-server/tenant1
  2. OpenID Connect Discovery 1.0 with path insertion: https://auth.example.com/.well-known/openid-configuration/tenant1
  3. OpenID Connect Discovery 1.0 path appending: https://auth.example.com/tenant1/.well-known/openid-configuration"

That same MCP section states the approach is based on RFC 8414 Section 3.1 ("Authorization Server Metadata Request") and RFC 8414 Section 5 ("Compatibility Notes") for OpenID Connect Discovery 1.0 interoperability.
So priority #2 — the OIDC path-insertion location — is exactly what OidcProviderConfigurationEndpointFilter does not serve, while the OAuth filter already serves the analogous insertion location for its own
document.

To Reproduce

  1. Configure a SecurityFilterChain that registers OAuth2AuthorizationServerMetadataEndpointFilter and OidcProviderConfigurationEndpointFilter, with
    AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build() available on the per-request AuthorizationServerContext (issuer e.g. http://host:8080/tenant1).
  2. Start the server.
  3. GET http://host:8080/.well-known/oauth-authorization-server/tenant1 — returns 200 (OAuth metadata, path insertion).
  4. GET http://host:8080/tenant1/.well-known/openid-configuration — returns 200 (OIDC metadata, path append).
  5. GET http://host:8080/.well-known/openid-configuration/tenant1 — returns 404 (OIDC metadata, path insertion), even though an MCP client following the spec's priority order tries this URL before the append form.

Expected behavior

In multi-issuer mode, OidcProviderConfigurationEndpointFilter should also serve the OIDC path-insertion location /.well-known/openid-configuration/**, symmetric with OAuth2AuthorizationServerMetadataEndpointFilter,
so that GET https://host/.well-known/openid-configuration/tenant1 returns the same document as the path-append location. Alternatively (or additionally), the filter should expose a way to configure its
RequestMatcher; it is currently final with a private final RequestMatcher and only setProviderConfigurationCustomizer is available, so applications cannot add the insertion location without wrapping/rewriting the
request.

The document body itself needs no change: the filter builds it solely from AuthorizationServerContextHolder.getContext().getIssuer(), so only the request matching (and the corresponding multi-issuer issuer
resolution, which must recognize that for the insertion shape the issuer path follows the well-known segment rather than preceding it) needs to handle both shapes.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions