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
+64 -1
View File
@@ -177,7 +177,8 @@
"standardFlowEnabled": false,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": false,
"serviceAccountsEnabled": false,
"serviceAccountsEnabled": true,
"authorizationServicesEnabled": true,
"publicClient": false,
"protocol": "openid-connect",
"attributes": {
@@ -186,6 +187,56 @@
"client.token.exchange.standard.enabled": "true",
"standard.token.exchange.enabled": "true"
},
"authorizationSettings": {
"allowRemoteResourceManagement": true,
"policyEnforcementMode": "ENFORCING",
"resources": [
{
"name": "token-exchange",
"type": "urn:keycloak:token-exchange",
"ownerManagedAccess": false,
"displayName": "Token Exchange",
"attributes": {},
"uris": [],
"scopes": [
{
"name": "token-exchange"
}
]
}
],
"policies": [
{
"name": "allow-nextcloud-mcp-server-to-exchange",
"description": "",
"type": "client",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"clients": "[\"nextcloud-mcp-server\",\"nextcloud\"]"
}
},
{
"name": "token-exchange-permission",
"description": "",
"type": "scope",
"logic": "POSITIVE",
"decisionStrategy": "AFFIRMATIVE",
"config": {
"resources": "[\"token-exchange\"]",
"scopes": "[\"token-exchange\"]",
"applyPolicies": "[\"allow-nextcloud-mcp-server-to-exchange\"]"
}
}
],
"scopes": [
{
"name": "token-exchange",
"displayName": "Token Exchange"
}
],
"decisionStrategy": "UNANIMOUS"
},
"fullScopeAllowed": true,
"nodeReRegistrationTimeout": -1
},
@@ -229,6 +280,18 @@
"fullScopeAllowed": true,
"nodeReRegistrationTimeout": -1,
"protocolMappers": [
{
"name": "mcp-server-audience",
"protocol": "openid-connect",
"protocolMapper": "oidc-audience-mapper",
"consentRequired": false,
"config": {
"included.client.audience": "nextcloud-mcp-server",
"access.token.claim": "true",
"id.token.claim": "false",
"introspection.token.claim": "true"
}
},
{
"name": "nextcloud-audience",
"protocol": "openid-connect",