619d0e4be6
The token-exchange-nextcloud client scope was being inherited by DCR clients
regardless of configuration, causing all tokens to have incorrect audience.
This commit removes the scope entirely and updates audience validation to be
more flexible.
## Problem
1. **DCR clients inherited token-exchange-nextcloud scope**
- Even after removing from nextcloud-mcp-server client's optional scopes
- Even though not in realm's default optional scopes
- Keycloak was adding all defined client scopes to DCR clients
2. **After removing audience mappers, tokens had no audience**
- Keycloak doesn't automatically populate aud from RFC 8707 resource parameter
- MCP server rejected tokens: "wrong audience [], expected nextcloud-mcp-server"
## Solution
### 1. Remove token-exchange-nextcloud Client Scope Entirely
- Delete the scope definition from realm-export.json
- Prevents it from being inherited by DCR clients
- audience is now set directly on nextcloud-mcp-server client via protocol mapper
### 2. Update Audience Validation Logic
Make progressive_token_verifier.py more flexible:
**Before**: Strict validation - reject if aud != mcp_client_id
```python
if self.mcp_client_id not in audiences:
return None # Reject
```
**After**: Flexible validation
- ✅ Accept tokens with no audience claim
- ✅ Accept tokens with MCP client ID in audience
- ✅ Accept tokens with resource URL in audience
- ❌ Reject tokens with "nextcloud" audience (wrong flow)
```python
if audiences:
if "nextcloud" in audiences:
return None # Wrong flow
# Accept other audiences (may use resource URL)
else:
# Accept tokens without audience
```
## Behavior
**External MCP Clients (Gemini CLI)**:
- Register via DCR → No token-exchange-nextcloud scope inherited ✅
- Request token → No audience mappers applied
- Token: `aud` absent or based on resource parameter
- MCP server: Accepts token ✅
**MCP Server (nextcloud-mcp-server) → Nextcloud APIs**:
- Has direct nextcloud-audience protocol mapper
- Token: `aud: "nextcloud"` (hardcoded on client)
- Nextcloud user_oidc: Validates successfully ✅
## Security
Token validation still enforces:
- Signature verification (via IdP JWKS)
- Expiration checks
- Issuer validation
- Scope-based authorization
- Explicitly rejects tokens meant for Nextcloud (aud: "nextcloud")
Accepting tokens without audience is safe because:
- External IdP (Keycloak) validates token issuance
- MCP server can fall back to introspection for opaque tokens
- RFC 9068 JWT Profile allows empty audience for resource servers
## Related
- RFC 8707: Resource Indicators for OAuth 2.0
- RFC 9068: JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens
- Keycloak DCR client scope inheritance behavior
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>