-
Notifications
You must be signed in to change notification settings - Fork 615
[BUG][AUTH]: OAuth2 with Microsoft Entra v2 fails with resource+scope conflict (AADSTS9010010) #2881
Description
🐞 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
- Deploy
mcpgatewaybehind HTTPS at a public URL such ashttps://gateway.example.com(e.g. on Kubernetes). - 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.urlpoints to the MCP server; no explicitresourceis configured inoauth_config.
- Authorization endpoint: Entra v2 authorize endpoint (for example
- Log in to the admin UI with local email/password at
/admin/login(this works as expected). - Navigate to the Gateways page in the admin UI.
- Click the Authorize link/button for the Entra‑backed gateway (which calls
GET /oauth/authorize/{gateway_id}and then redirects the browser to Entra). - 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=...
- Observe that no
codeparameter is present on the callback URL, and the gateway returns a FastAPI validation error because thecodequery 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/callbackwith a validcodeandstate. - The
/oauth/callbackroute 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"] = resourceWhen 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.
- Redirect URI:
- The current implementation of the gateway:
- Always includes a
resourceparameter in the authorization request if present inoauth_config, and falls back to deriving it fromgateway.urlwhen missing. - Also includes
scopein 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
resourceis allowed as an optional extension, but support is provider-specific; in this case the provider (Microsoft identity platform v2.0) explicitly does not support legacyresourcein combination with v2-stylescopeparameters.
- Always includes a