Skip to content

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

Closed

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.

Activity

  1. added
    bugSomething isn't working
    triageIssues / Features awaiting triage
    on Feb 12, 2026
  2. added
    securityImproves security
    apiREST API Related item
    SHOULDP2: Important but not vital; high-value items that are not crucial for the immediate release
    and removed
    triageIssues / Features awaiting triage
    on Feb 12, 2026
  3. added this to the Release 1.1.0 milestone on Feb 12, 2026
  4. changed the title [-][BUG]: OAuth2 with Microsoft Entra v2: gateway always sends resource+scope, Entra returns AADSTS9010010 (invalid_target)[/-] [+][BUG][AUTH]: OAuth2 with Microsoft Entra v2 fails with resource+scope conflict (AADSTS9010010)[/+] on Feb 12, 2026
  5. crivetimihai commented on Feb 12, 2026

    @crivetimihai
    Member

    Thanks for the thorough writeup with root cause analysis. Confirmed both issues in the code:

    1. Resource parameter: oauth_manager.py:990-998 in _create_authorization_url_with_pkce() unconditionally adds the resource parameter. There's no conditional logic to skip it for Entra v2 endpoints. The resource is derived from gateway.url when not explicitly configured (oauth_router.py:155). Fix: make resource conditional — omit when using v2-style scope parameters, or add an explicit config flag like omit_resource: true.

    2. Callback error handling: oauth_router.py:248-250 requires code as a mandatory query parameter. Per RFC 6749 Section 4.1.2.1, error responses send error and error_description instead of code. Fix: make code optional and add error/error_description optional parameters, then return a user-friendly error page when error is present.

  6. added theissue type on Feb 12, 2026
  7. ankit-gajera-merck commented on Feb 13, 2026

    @ankit-gajera-merck
    Contributor

    Hi @crivetimihai & Team, I have fixed the bug and raised the PR #2899, it would be great if you can evaluate and provide the fix in GA Release rather 1.1.0.

  8. razashariff commented on Mar 16, 2026

    @razashariff

    OAuth provider compatibility issues like this are a recurring problem for MCP gateways. MCPS (MCP Secure) provides a simpler, more reliable authentication mechanism -- cryptographic signatures that do not depend on complex OAuth/OIDC token exchange flows that break across providers.

    Each agent holds an ECDSA P-256 key pair and a signed passport. Authentication happens per-message via signature verification. No token endpoints, no redirect URIs, no provider-specific configuration. Works identically regardless of the identity provider backend.

    MCPS can complement OAuth (passport issued after successful OAuth) or replace it entirely for agent-to-server authentication in environments where OAuth integration is unreliable.

    npm: mcp-secure (zero deps). IETF: draft-sharif-mcps-secure-mcp. agentsign.dev

  9. removed
    bugSomething isn't working
    on Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

SHOULDP2: Important but not vital; high-value items that are not crucial for the immediate releaseapiREST API Related itemsecurityImproves security

Type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions