Implemented user-based authentication for the Keycloak MCP Server using JWT Bearer tokens. This eliminates the security flaw where users could access Keycloak without proper authentication and enables Keycloak’s native permission enforcement.
Implementation Date: November 21, 2024 Version: 0.4.0 Status: Complete
All MCP server operations must enforce user-specific permissions through Keycloak’s native access control system.
Implement JWT Bearer token authentication where:
User → Keycloak (authenticate)
↓
User receives JWT token
↓
User → MCP Server (with JWT in Authorization header)
↓
MCP Server validates JWT token (OIDC)
↓
MCP Server → Keycloak Admin API (using user's token)
↓
Keycloak enforces user's permissions
File: pom.xml
<!-- Security and Authentication -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-security</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-health</artifactId>
</dependency>
Purpose: Enable OIDC token validation, security policies, and health checks
File: src/main/resources/application.properties
# OIDC Configuration - Token Validation
quarkus.oidc.auth-server-url=${KC_URL}/realms/${KC_REALM:master}
quarkus.oidc.client-id=${OIDC_CLIENT_ID:mcp-server}
quarkus.oidc.application-type=service
quarkus.oidc.token.issuer=${KC_URL}/realms/${KC_REALM:master}
# Security Policies - Require authentication for MCP endpoints
quarkus.http.auth.permission.mcp.paths=/mcp/*
quarkus.http.auth.permission.mcp.policy=authenticated
# Public endpoints (health checks, metrics)
quarkus.http.auth.permission.public.paths=/q/*
quarkus.http.auth.permission.public.policy=permit
Purpose:
File: src/main/java/dev/shaaf/keycloak/mcp/server/KeycloakClientFactory.java (NEW)
Purpose: Create Keycloak admin clients using the authenticated user’s JWT token
Key Features:
Code Structure:
@RequestScoped
public class KeycloakClientFactory {
@Inject SecurityIdentity securityIdentity;
@Inject JsonWebToken jwt;
public Keycloak createClient() {
// Requires user authentication
if (!securityIdentity.isAnonymous() && jwt != null) {
return KeycloakBuilder.builder()
.serverUrl(serverUrl)
.realm(realm)
.authorization("Bearer " + jwt.getRawToken())
.build();
}
throw new IllegalStateException("Authentication required");
}
}
Updated Files: All 7 service classes:
UserService.javaRealmService.javaClientService.javaRoleService.javaGroupService.javaIdentityProviderService.javaAuthenticationFlowService.javaChanges:
Keycloak injection with KeycloakClientFactoryclientFactory.createClient()File: scripts/get-mcp-token.sh
Purpose: Help users obtain JWT tokens and generate MCP configuration
Features:
Usage:
./scripts/get-mcp-token.sh \
--keycloak-url https://keycloak.example.com \
--username your-username \
--password your-password
File: deploy/openshift/deployment.yaml
Environment Variables:
env:
- name: KC_URL
valueFrom:
configMapKeyRef:
name: keycloak-mcp-config
key: keycloak-url
- name: KC_REALM
valueFrom:
configMapKeyRef:
name: keycloak-mcp-config
key: keycloak-realm
- name: OIDC_CLIENT_ID
valueFrom:
configMapKeyRef:
name: keycloak-mcp-config
key: client-id
Note: No secrets required. Token validation uses public OIDC discovery.
File: ~/.cursor/mcp.json
{
"mcpServers": {
"keycloak": {
"transport": "sse",
"url": "https://mcp-server.example.com/mcp/sse",
"headers": {
"Authorization": "Bearer <user-jwt-token>"
}
}
}
}
Users obtain their token using the helper script:
./scripts/get-mcp-token.sh \
--keycloak-url https://keycloak.example.com \
--username their-username \
--password their-password
The script outputs the complete MCP configuration with the token.
Each user’s permissions are enforced by Keycloak:
Keycloak logs show:
oc apply -f deploy/openshift/
./scripts/get-mcp-token.sh --keycloak-url <url> --username <user>
Configure MCP Client
Update ~/.cursor/mcp.json with user token
Successfully implemented JWT Bearer token authentication for the Keycloak MCP Server. The implementation:
The MCP server now operates as a transparent, authenticated proxy that enforces Keycloak’s existing permission system, making it suitable for production use with proper access control.
Status: Implementation Complete Version: 0.4.0 Date: November 21, 2024