Files
mcp-nextcloud/fix_keycloak_realm_audience.sh
T
Chris CoutinhoandClaude 11cdab475f feat: unify session architecture and enhance login status visibility
This commit addresses the "Login not detected" issue after completing
OAuth login via elicitation by unifying the session architecture and
adding comprehensive visibility into background session status.

## Changes

### 1. Enhanced check_logged_in with comprehensive logging (oauth_tools.py)
- Added detailed logging at each step of token lookup
- Implemented fallback strategy: first search by provisioning_client_id,
  then fall back to user_id lookup
- This allows detection of refresh tokens created via any flow
  (elicitation or browser login)
- Log messages include flow_type, provisioned_at, and provisioning_client_id
  for debugging

### 2. Unified session architecture (browser_oauth_routes.py)
- Browser login now stores provisioning_client_id=state when saving
  refresh token
- This makes browser and elicitation flows consistent - both can be
  found by the same state parameter
- Treats Flow 2 (elicitation) and browser login as the same "background
  session"

### 3. Enhanced /user/page with session status (userinfo_routes.py)
- Added comprehensive background access section showing:
  - Background Access: Granted/Not Granted (with visual indicators)
  - Flow Type: browser/flow2/hybrid
  - Provisioned At: timestamp
  - Token Audience: nextcloud/mcp
  - Scopes: detailed scope list
- Status displayed regardless of which flow created the session
  (browser login or elicitation)

### 4. Added revoke functionality (userinfo_routes.py, app.py)
- New POST endpoint: /user/revoke
- Allows users to revoke background access (delete refresh token)
- Browser session cookie remains valid for UI access
- Confirmation dialog before revocation
- Success page with auto-redirect back to /user/page
- Registered route in app.py browser_routes

## Testing
All tests pass:
- 6/6 login elicitation tests pass
- 21/21 core OAuth tests pass
- Comprehensive logging helps debug future issues

## Fixes
Resolves: "Login not detected. Please ensure you completed the login
at the provided URL before clicking OK."

The issue occurred because elicitation and browser login created
separate sessions. Now they are unified under the same architecture.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-07 21:50:55 +01:00

112 lines
4.0 KiB
Bash
Executable File

#!/bin/bash
echo "Applying audience fix to Keycloak realm for ALL clients..."
# Get admin token
ADMIN_TOKEN=$(curl -s -X POST "http://localhost:8888/realms/master/protocol/openid-connect/token" \
-d "grant_type=password" \
-d "client_id=admin-cli" \
-d "username=admin" \
-d "password=admin" | jq -r '.access_token')
if [ -z "$ADMIN_TOKEN" ] || [ "$ADMIN_TOKEN" == "null" ]; then
echo "Failed to get admin token. Is Keycloak running?"
exit 1
fi
echo "Got admin token"
# Create a default client scope with audience mapper that will apply to ALL clients
echo "Creating default audience scope..."
# First, delete if it exists
curl -s -X DELETE "http://localhost:8888/admin/realms/nextcloud-mcp/client-scopes/default-audience" \
-H "Authorization: Bearer $ADMIN_TOKEN" 2>/dev/null
# Create new client scope
SCOPE_RESPONSE=$(curl -s -X POST "http://localhost:8888/admin/realms/nextcloud-mcp/client-scopes" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "default-audience",
"protocol": "openid-connect",
"attributes": {
"include.in.token.scope": "false",
"display.on.consent.screen": "false"
},
"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"
}
},
{
"name": "mcp-url-audience",
"protocol": "openid-connect",
"protocolMapper": "oidc-audience-mapper",
"consentRequired": false,
"config": {
"included.custom.audience": "http://localhost:8002",
"access.token.claim": "true",
"id.token.claim": "false"
}
}
]
}')
# Get the scope ID
SCOPE_ID=$(curl -s -X GET "http://localhost:8888/admin/realms/nextcloud-mcp/client-scopes" \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq -r '.[] | select(.name == "default-audience") | .id')
if [ -z "$SCOPE_ID" ] || [ "$SCOPE_ID" == "null" ]; then
echo "Failed to create client scope"
exit 1
fi
echo "Created client scope with ID: $SCOPE_ID"
# Make this a default client scope (applies to ALL clients automatically)
curl -s -X PUT "http://localhost:8888/admin/realms/nextcloud-mcp/default-default-client-scopes/$SCOPE_ID" \
-H "Authorization: Bearer $ADMIN_TOKEN"
echo "Made it a default client scope"
# Now update ALL existing clients to use this scope
echo "Updating existing clients..."
# Get all clients
CLIENTS=$(curl -s -X GET "http://localhost:8888/admin/realms/nextcloud-mcp/clients" \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq -r '.[] | select(.clientId != "admin-cli" and .clientId != "account" and .clientId != "broker" and .clientId != "realm-management" and .clientId != "security-admin-console" and .clientId != "account-console") | .id')
for CLIENT_ID in $CLIENTS; do
CLIENT_NAME=$(curl -s -X GET "http://localhost:8888/admin/realms/nextcloud-mcp/clients/$CLIENT_ID" \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq -r '.clientId')
echo " Adding scope to client: $CLIENT_NAME"
# Add the default scope to this client
curl -s -X PUT "http://localhost:8888/admin/realms/nextcloud-mcp/clients/$CLIENT_ID/default-client-scopes/$SCOPE_ID" \
-H "Authorization: Bearer $ADMIN_TOKEN"
done
echo ""
echo "Testing with a new token..."
TOKEN=$(curl -s -X POST 'http://localhost:8888/realms/nextcloud-mcp/protocol/openid-connect/token' \
-d 'grant_type=password' \
-d 'client_id=nextcloud-mcp-server' \
-d 'client_secret=mcp-secret-change-in-production' \
-d 'username=admin' \
-d 'password=admin' | jq -r '.access_token')
echo "Token audience:"
echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print('aud:', d.get('aud', 'NO AUD'))"
echo ""
echo "✅ Audience configuration applied to ALL clients in the realm!"
echo "New clients registered by Gemini will automatically get these audiences."