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:
Chris Coutinho
2025-11-04 03:06:11 +01:00
parent 01d1cf9190
commit 192c4bf009
2 changed files with 81 additions and 8 deletions
+5 -7
View File
@@ -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"],