Skip to content

[BUG][AUTH]: OAuth2 with Microsoft Entra v2 fails with resource+scope conflict (AADSTS9010010) #2881

@asasar

Description

@asasar

🐞 Bug Summary

When using Microsoft Entra ID (Azure AD) as OAuth2 provider for a configured gateway (MCP server), clicking Authorize in the Context Forge admin UI redirects to Entra but the authorization code flow fails. Entra returns error=invalid_target (AADSTS9010010), and the gateway’s /oauth/callback endpoint then errors because the mandatory code query parameter is missing. The error from Entra appears to be caused by the gateway always including a resource parameter (derived from gateway.url or oauth_config["resource"]) in addition to scope when using the Entra v2 authorize endpoint (/oauth2/v2.0/authorize), while the Microsoft identity platform v2.0 endpoint does not support the legacy resource parameter in combination with v2-style scope parameters.


🧩 Affected Component

Select the area of the project impacted:

  • mcpgateway - API
  • mcpgateway - UI (admin panel)
  • mcpgateway.wrapper - stdio wrapper
  • Federation or Transports
  • CLI, Makefiles, or shell scripts
  • Container setup (Docker/Podman/Compose)
  • Other (explain below)

🔁 Steps to Reproduce

  1. Deploy mcpgateway behind HTTPS at a public URL such as https://gateway.example.com (e.g. on Kubernetes).
  2. Configure a gateway (MCP server) in the admin UI with OAuth2 / OIDC using Microsoft Entra ID:
    • Authorization endpoint: Entra v2 authorize endpoint (for example .../oauth2/v2.0/authorize).
    • Client ID / secret and redirect URI set to https://gateway.example.com/oauth/callback.
    • OAuth scopes configured for the Entra application (v2-style scopes).
    • gateway.url points to the MCP server; no explicit resource is configured in oauth_config.
  3. Log in to the admin UI with local email/password at /admin/login (this works as expected).
  4. Navigate to the Gateways page in the admin UI.
  5. Click the Authorize link/button for the Entra‑backed gateway (which calls GET /oauth/authorize/{gateway_id} and then redirects the browser to Entra).
  6. After interacting with Entra (or immediately), Entra redirects back to:
    • https://gateway.example.com/oauth/callback?error=invalid_target&error_description=AADSTS9010010%3a+The+resource+parameter+provided+in+the+request+doesn%27t+match+with+the+requested+scopes...&state=...
  7. Observe that no code parameter is present on the callback URL, and the gateway returns a FastAPI validation error because the code query parameter is required.

🤔 Expected Behavior

  • When clicking Authorize for a gateway configured with Microsoft Entra ID:
    • The user should be redirected to Entra.
    • After successful consent, Entra should redirect to https://gateway.example.com/oauth/callback with a valid code and state.
    • The /oauth/callback route should complete the OAuth authorization code flow, store the tokens for that gateway and user, and display a success or status page to the user.

📓 Logs / Error Output

1. Callback from Entra to /oauth/callback without code:

Browser URL (simplified and anonymized):

https://gateway.example.com/oauth/callback
  ?error=invalid_target
  &error_description=AADSTS9010010%3a+The+resource+parameter+provided+in+the+request+doesn%27t+match+with+the+requested+scopes...
  &state=eyJnYXRld2F5X2lkIjoiMTk2NTI3N2ExN2JjNDhjYjkyOTZlYmVhZmUzYTI1MmYiLCJhcHBfdXNlcl9lbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwibm9uY2UiOiIzLVVjVFFoQ3JyQUlJUDRoaFVrcHJRIiwidGltZXN0YW1wIjoiMjAyNi0wMi0xMlQxMjoxOTowMC4zOTU2ODgrMDA6MDAifQ...
  #
{"detail":[{"type":"missing","loc":["query","code"],"msg":"Field required","input":null}]}

The JSON body is the FastAPI error for the missing code query parameter on /oauth/callback.

Corresponding route definition:

# mcpgateway/routers/oauth_router.py

@oauth_router.get("/callback")
async def oauth_callback(
    code: str = Query(..., description="Authorization code from OAuth provider"),
    state: str = Query(..., description="State parameter for CSRF protection"),
    request: Request = None,
    db: Session = Depends(get_db),
) -> HTMLResponse:
    ...

2. Where the gateway constructs resource + scope for OAuth:

# mcpgateway/routers/oauth_router.py (simplified excerpts)

if oauth_config.get("resource"):
    ...
else:
    # Default to gateway.url as the resource (strip query per RFC 8707 SHOULD NOT)
    oauth_config["resource"] = _normalize_resource_url(gateway.url)
# mcpgateway/services/oauth_manager.py

def _create_authorization_url_with_pkce(...):
    params = {
        "response_type": "code",
        "client_id": client_id,
        "redirect_uri": redirect_uri,
        "state": state,
        "code_challenge": code_challenge,
        "code_challenge_method": code_challenge_method,
    }

    if scopes:
        params["scope"] = " ".join(scopes) if isinstance(scopes, list) else scopes

    # Add resource parameter for JWT access token (RFC 8707)
    resource = credentials.get("resource")
    if resource:
        if isinstance(resource, list):
            params["resource"] = resource
        else:
            params["resource"] = resource

When using Microsoft Entra ID with the v2 authorize endpoint (/oauth2/v2.0/authorize) and standard v2 scopes, this results in authorization requests that always contain both scope and resource, and Entra responds with AADSTS9010010 (invalid_target) and no code. The behavior is consistent with Microsoft’s documentation for the v2.0 identity platform, which does not support the legacy resource parameter when using v2-style scope parameters.


🧠 Environment Info

Values are based on the deployment where the issue occurs; exact version/commit should be confirmed via /version.

Key Value
Version or commit main (exact commit unknown at report time)
Runtime Python 3.12, Uvicorn + FastAPI (in container)
Platform / OS Kubernetes cluster (Linux nodes), client from Windows 10
Container Yes (Docker image running in Kubernetes)

🧩 Additional Context (optional)

  • OAuth provider: Microsoft Entra ID (Azure AD).
  • The Entra application is configured with:
    • Redirect URI: https://gateway.example.com/oauth/callback.
    • Standard OAuth2 / OIDC scopes configured (v2 style).
    • Entra returns error=invalid_target / AADSTS9010010: The resource parameter provided in the request doesn't match with the requested scopes.
  • The current implementation of the gateway:
    • Always includes a resource parameter in the authorization request if present in oauth_config, and falls back to deriving it from gateway.url when missing.
    • Also includes scope in the same request, which appears to be incompatible with Entra’s v2 endpoint (/oauth2/v2.0/authorize) when using v2-style scopes.
    • According to OAuth 2.0 and RFC 8707, using resource is allowed as an optional extension, but support is provider-specific; in this case the provider (Microsoft identity platform v2.0) explicitly does not support legacy resource in combination with v2-style scope parameters.

Metadata

Metadata

Labels

SHOULDP2: Important but not vital; high-value items that are not crucial for the immediate releaseapiREST API Related itembugSomething isn't workingclient-greenclient-greensecurityImproves security

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions