fix: correct OAuth token audience validation using RFC 8707 resource parameter
The test_mcp_oauth_server_connection test was failing because OAuth tokens had the wrong audience claim. The MCP server's progressive_token_verifier expects tokens with audience matching its OAuth client ID, but tokens were being issued with Nextcloud's default resource server audience. Changes: 1. Test fixtures (tests/conftest.py): - Add get_mcp_server_resource_metadata() helper to fetch PRM metadata - Update playwright_oauth_token to include resource parameter in auth requests - Update _get_oauth_token_with_scopes to support optional resource parameter - Automatically fetch resource ID from MCP server's PRM endpoint 2. MCP Server (nextcloud_mcp_server/app.py): - Fix Protected Resource Metadata endpoint to return OAuth client ID - Change "resource" field from URL to client ID for proper audience validation - Ensures tokens obtained with resource parameter have correct audience claim How it works: 1. Test fetches /.well-known/oauth-protected-resource from MCP server 2. Extracts resource field (MCP server's client ID) 3. Includes &resource=<client-id> in OAuth authorization request (RFC 8707) 4. Nextcloud OIDC issues tokens with aud: [<client-id>] 5. MCP server's progressive_token_verifier accepts tokens (audience matches) Fixes OAuth test failures: - test_mcp_oauth_server_connection - test_mcp_oauth_tool_execution - test_mcp_oauth_client_with_playwright 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -930,13 +930,11 @@ def get_app(transport: str = "sse", enabled_apps: list[str] | None = None):
|
||||
|
||||
Dynamically discovers supported scopes from registered MCP tools.
|
||||
This ensures the advertised scopes always match the actual tool requirements.
|
||||
"""
|
||||
mcp_server_url = os.getenv(
|
||||
"NEXTCLOUD_MCP_SERVER_URL", "http://localhost:8000"
|
||||
)
|
||||
# Append /mcp to match the actual resource path (FastMCP streamable-http endpoint)
|
||||
resource_url = f"{mcp_server_url}/mcp"
|
||||
|
||||
The 'resource' field is set to the MCP server's OAuth client ID, which is
|
||||
used as the audience claim in access tokens. This ensures tokens obtained
|
||||
with the resource parameter match the audience validation in progressive_token_verifier.
|
||||
"""
|
||||
# Use PUBLIC_ISSUER_URL for authorization server since external clients
|
||||
# (like Claude) need the publicly accessible URL, not internal Docker URLs
|
||||
public_issuer_url = os.getenv("NEXTCLOUD_PUBLIC_ISSUER_URL")
|
||||
@@ -950,7 +948,7 @@ def get_app(transport: str = "sse", enabled_apps: list[str] | None = None):
|
||||
|
||||
return JSONResponse(
|
||||
{
|
||||
"resource": resource_url,
|
||||
"resource": client_id, # MCP server's OAuth client ID (for audience validation)
|
||||
"scopes_supported": supported_scopes,
|
||||
"authorization_servers": [public_issuer_url],
|
||||
"bearer_methods_supported": ["header"],
|
||||
|
||||
Reference in New Issue
Block a user