feat: enable authorization services for token exchange in Keycloak

Configure Keycloak authorization policies to allow nextcloud-mcp-server
to exchange tokens for nextcloud audience. This enables RFC 8693 token
exchange flow between the MCP client and Nextcloud.

Changes:
- Enable service accounts and authorization services for nextcloud client
- Add token-exchange resource with scope-based permissions
- Create client policy allowing nextcloud-mcp-server and nextcloud
- Add token-exchange-permission with affirmative decision strategy
- Add mcp-server-audience mapper to nextcloud-mcp-server client
- Simplify audience validation to accept tokens with MCP client ID

The authorization policy enables tokens issued to nextcloud-mcp-server
to be exchanged for tokens with nextcloud audience, validated via both
clients being included in the allow-nextcloud-mcp-server-to-exchange
policy.

All 7 token exchange integration tests pass, confirming:
- Basic token exchange with correct audience claims
- Nextcloud API access with exchanged tokens
- Stateless multiple exchange operations
- Full CRUD operations on Notes API
- Proper claim preservation (sub, azp, aud)
- Default scope configuration
- TokenExchangeService implementation

🤖 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 08:34:51 +01:00
parent 619d0e4be6
commit 723eb57060
2 changed files with 73 additions and 14 deletions
@@ -140,27 +140,23 @@ class ProgressiveConsentTokenVerifier:
# Audience validation:
# - Accept tokens with no audience (will validate via introspection if needed)
# - Accept tokens with MCP client ID in audience
# - Reject tokens with "nextcloud" audience (wrong flow)
# - Accept tokens with MCP client ID in audience (regardless of other audiences)
# - Reject tokens without MCP client ID (if audience is present)
if audiences:
# Check if this is a Nextcloud token (wrong flow)
if "nextcloud" in audiences:
# Check if MCP client ID is in the audience
if self.mcp_client_id in audiences:
logger.debug(
f"Token has audience {audiences} - MCP client ID present"
)
else:
logger.warning(
f"Token rejected: wrong audience {audiences}, expected {self.mcp_client_id} or no audience"
)
logger.error(
"Received Nextcloud token in MCP context - "
"Token does not include MCP client ID in audience - "
"client may be using wrong token"
)
return None
# If audience is present but doesn't match, log warning but continue
# (token might use resource URL instead of client ID)
if self.mcp_client_id not in audiences:
logger.info(
f"Token has audience {audiences}, expected {self.mcp_client_id}. "
"Accepting token with non-standard audience (may use resource URL)."
)
else:
logger.info(
"Token has no audience claim - accepting for MCP server validation"