Keycloak Authentication Guide for Solr MCP Server

This guide covers setting up Keycloak as an OAuth2/OpenID Connect identity provider for the Solr MCP Server running in HTTP mode.

Table of Contents

Overview

The Solr MCP Server supports OAuth2 authentication via JWT tokens. Keycloak acts as the authorization server, issuing tokens that the MCP server validates. This enables:

  • Centralized user management
  • Single Sign-On (SSO) across multiple applications
  • Integration with external identity providers (GitHub, Google, LDAP, etc.)
  • Fine-grained role-based access control

Authentication Flow

User                    MCP Client            Keycloak              Solr MCP Server
  │                        │                     │                        │
  │─── Request Access ────►│                     │                        │
  │                        │─── Auth Request ───►│                        │
  │◄────────────────────── Login Page ◄──────────│                        │
  │─── Credentials ───────────────────────────────►                       │
  │                        │◄─── JWT Token ──────│                        │
  │                        │─── API Request + Token ────────────────────►│
  │                        │                     │      (validates JWT)   │
  │                        │◄─────────────────────────── Response ────────│
  │◄─── Result ────────────│                     │                        │

Prerequisites

  • Docker (for running Keycloak)
  • Java 25 (for Solr MCP Server)
  • Gradle (for building the server)

Quick Start

# 1. Start Keycloak
docker run -d --name keycloak \
  -p 8180:8080 \
  -e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
  -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \
  quay.io/keycloak/keycloak:26.0 start-dev

# 2. Configure Keycloak (see detailed steps below)
# - Create realm: solr-mcp
# - Create client: solr-mcp-client

# 3. Run Solr MCP Server with security enabled
export PROFILES=http
export HTTP_SECURITY_ENABLED=true
export OAUTH2_ISSUER_URI=http://localhost:8180/realms/solr-mcp
./gradlew bootRun

Keycloak Setup

Running Keycloak

Development Mode (Docker):

docker run -d --name keycloak \
  -p 8180:8080 \
  -e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
  -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \
  quay.io/keycloak/keycloak:26.0 start-dev

Access the admin console at http://localhost:8180 and log in with admin/admin.

Production Mode:

For production deployments, use a proper database backend and TLS:

docker run -d --name keycloak \
  -p 8443:8443 \
  -e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
  -e KC_BOOTSTRAP_ADMIN_PASSWORD=<secure-password> \
  -e KC_DB=postgres \
  -e KC_DB_URL=jdbc:postgresql://db-host:5432/keycloak \
  -e KC_DB_USERNAME=keycloak \
  -e KC_DB_PASSWORD=<db-password> \
  -e KC_HOSTNAME=keycloak.example.com \
  quay.io/keycloak/keycloak:26.0 start

Creating a Realm

  1. Log into Keycloak Admin Console: http://localhost:8180/admin
  2. Click the dropdown in the top-left (shows “master”)
  3. Click Create realm
  4. Realm name: solr-mcp
  5. Click Create

Creating Clients

You need at least one client for applications to authenticate against.

Resource Server Client (for the MCP Server)

  1. Navigate to ClientsCreate client
  2. Configure:
    • Client ID: solr-mcp-server
    • Client type: OpenID Connect
  3. Click Next
  4. Client authentication: ON (confidential client)
  5. Authentication flow: Enable Service accounts roles
  6. Click NextSave

Public Client (for testing/MCP Inspector)

  1. Navigate to ClientsCreate client
  2. Configure:
    • Client ID: solr-mcp-client
    • Client type: OpenID Connect
  3. Click Next
  4. Client authentication: OFF (public client)
  5. Click Next
  6. Configure access settings:
    • Valid redirect URIs: http://localhost:6274/*, http://localhost:*
    • Web origins: * or http://localhost:6274
  7. Click Save

Creating Test Users

  1. Navigate to UsersAdd user
  2. Configure:
    • Username: testuser
    • Email: test@example.com
    • Email verified: ON
  3. Click Create
  4. Go to the Credentials tab
  5. Click Set password
  6. Enter password and disable Temporary
  7. Click Save

Spring Boot Configuration

The Solr MCP Server is pre-configured to work with any OAuth2/OIDC provider. Update application-http.properties:

# Security toggle - set to true to enable OAuth2 authentication
http.security.enabled=${HTTP_SECURITY_ENABLED:true}

# Keycloak OAuth2 Configuration
# Format: https://<keycloak-host>/realms/<realm-name>
spring.security.oauth2.resourceserver.jwt.issuer-uri=${OAUTH2_ISSUER_URI:http://localhost:8180/realms/solr-mcp}

No code changes are required—the existing McpServerConfiguration handles JWT validation automatically by discovering Keycloak's JWKS endpoint from the issuer URI.

Running the Server

With Security Enabled:

export PROFILES=http
export HTTP_SECURITY_ENABLED=true
export OAUTH2_ISSUER_URI=http://localhost:8180/realms/solr-mcp
./gradlew bootRun

Without Security (Development Only):

export PROFILES=http
export HTTP_SECURITY_ENABLED=false
./gradlew bootRun

Testing Authentication

Obtain a Token

Using Resource Owner Password Grant (for testing):

curl -X POST "http://localhost:8180/realms/solr-mcp/protocol/openid-connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=solr-mcp-client" \
  -d "username=testuser" \
  -d "password=yourpassword" \
  -d "grant_type=password"

Response:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_in": 300,
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer"
}

Call the MCP Server

# Store token in variable
TOKEN=$(curl -s -X POST "http://localhost:8180/realms/solr-mcp/protocol/openid-connect/token" \
  -d "client_id=solr-mcp-client" \
  -d "username=testuser" \
  -d "password=yourpassword" \
  -d "grant_type=password" | jq -r '.access_token')

# Call MCP endpoint
curl -X POST http://localhost:8080/mcp \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'

User Management Options

Keycloak provides multiple ways to manage users beyond manual creation.

Manual User Creation

As described above, create users via UsersAdd user in the admin console.

Identity Brokering (GitHub, Google, etc.)

Allow users to authenticate via external identity providers. See GitHub Identity Provider Setup for a detailed example.

Supported providers include:

  • GitHub
  • Google
  • Microsoft/Azure AD
  • Facebook
  • Twitter/X
  • Any SAML 2.0 or OIDC provider

Self-Registration

Allow users to create their own accounts:

  1. Navigate to Realm settingsLogin tab
  2. Enable User registration
  3. Optionally enable:
    • Email verification
    • Terms and conditions
    • reCAPTCHA

REST API

Programmatically create users:

# Get admin token
TOKEN=$(curl -s -X POST "http://localhost:8180/realms/master/protocol/openid-connect/token" \
  -d "client_id=admin-cli" \
  -d "username=admin" \
  -d "password=admin" \
  -d "grant_type=password" | jq -r '.access_token')

# Create user
curl -X POST "http://localhost:8180/admin/realms/solr-mcp/users" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "newuser",
    "email": "newuser@example.com",
    "enabled": true,
    "emailVerified": true,
    "credentials": [{
      "type": "password",
      "value": "temppassword",
      "temporary": true
    }]
  }'

JSON Import

Bulk import users during realm setup:

{
  "realm": "solr-mcp",
  "users": [
    {
      "username": "user1",
      "email": "user1@example.com",
      "enabled": true,
      "credentials": [{"type": "password", "value": "pass123"}],
      "realmRoles": ["user", "solr-query"]
    },
    {
      "username": "user2",
      "email": "user2@example.com",
      "enabled": true,
      "credentials": [{"type": "password", "value": "pass456"}],
      "realmRoles": ["admin"]
    }
  ]
}

Import via CLI:

/opt/keycloak/bin/kc.sh import --file realm-export.json

Custom User Storage SPI

For databases or custom backends, implement Keycloak's User Storage SPI to authenticate against your existing user database without migration.

GitHub Identity Provider Setup

Step 1: Create GitHub OAuth App

  1. Go to GitHub → SettingsDeveloper settingsOAuth AppsNew OAuth App

    Direct link: https://github.com/settings/applications/new

  2. Fill in the form:

FieldValue
Application nameSolr MCP Server
Homepage URLhttp://localhost:8180
Authorization callback URLhttp://localhost:8180/realms/solr-mcp/broker/github/endpoint

Note: The callback URL format is: https://<keycloak-host>/realms/<realm-name>/broker/github/endpoint

  1. Click Register application
  2. Note the Client ID and generate a Client Secret

Step 2: Configure Keycloak

  1. Log into Keycloak Admin Console
  2. Select your realm (solr-mcp)
  3. Navigate to Identity ProvidersAdd providerGitHub
  4. Configure:
FieldValue
Client ID<from GitHub>
Client Secret<from GitHub>
Default Scopesuser:email (optional)
  1. Click Save

Step 3: Test GitHub Login

  1. Open: http://localhost:8180/realms/solr-mcp/account
  2. Click the GitHub button
  3. Authorize on GitHub
  4. You're logged into Keycloak with your GitHub account

Optional: Map GitHub Data

Add mappers to import GitHub profile data:

  1. Navigate to Identity ProvidersGitHubMappers
  2. Click Add mapper
  3. Example configurations:
NameMapper TypeClaimUser Attribute
GitHub UsernameAttribute Importerlogingithub_username
GitHub AvatarAttribute Importeravatar_urlavatar
GitHub EmailAttribute Importeremailemail

Optional: Auto-Assign Roles

Automatically assign roles to GitHub users:

  1. Navigate to Identity ProvidersGitHubMappers
  2. Click Add mapper
  3. Configure:
    • Mapper Type: Hardcoded Role
    • Role: Select a role (e.g., solr-query)

Role-Based Access Control (Optional)

To use Keycloak roles with Spring Security's @PreAuthorize annotations, add a JWT converter.

Create Roles in Keycloak

  1. Navigate to Realm rolesCreate role
  2. Create roles like admin, solr-query, solr-admin
  3. Assign roles to users via Users → select user → Role mappings

Configure Spring Security

Add to McpServerConfiguration.java:

import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;

@Bean
@ConditionalOnProperty(name = "http.security.enabled", havingValue = "true", matchIfMissing = true)
public JwtAuthenticationConverter jwtAuthenticationConverter() {
    JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
    // Keycloak stores realm roles in realm_access.roles
    grantedAuthoritiesConverter.setAuthoritiesClaimName("realm_access.roles");
    grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");

    JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
    jwtConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
    return jwtConverter;
}

Wire it into the security filter chain:

@Bean
@ConditionalOnProperty(name = "http.security.enabled", havingValue = "true", matchIfMissing = true)
SecurityFilterChain securityFilterChain(HttpSecurity http, JwtAuthenticationConverter jwtAuthenticationConverter) throws Exception {
    return http
        .authorizeHttpRequests(auth -> {
            auth.requestMatchers("/actuator").permitAll();
            auth.requestMatchers("/actuator/*").permitAll();
            auth.requestMatchers("/mcp").permitAll();
            auth.anyRequest().authenticated();
        })
        .oauth2ResourceServer(oauth2 -> oauth2
            .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter))
        )
        .with(McpServerOAuth2Configurer.mcpServerOAuth2(), (mcpAuthorization) -> {
            mcpAuthorization.authorizationServer(issuerUrl);
        })
        .cors(cors -> cors.configurationSource(corsConfigurationSource()))
        .csrf(CsrfConfigurer::disable)
        .build();
}

Use Role Annotations

@PreAuthorize("hasRole('admin')")
public String deleteCollection(String name) { ... }

@PreAuthorize("hasRole('solr-query')")
public String executeQuery(String collection, String query) { ... }

@PreAuthorize("hasAnyRole('solr-query', 'solr-admin')")
public String getSchema(String collection) { ... }

Keycloak Role Locations

LocationClaim PathUse Case
Realm rolesrealm_access.rolesGlobal roles across all clients
Client rolesresource_access.<client-id>.rolesRoles specific to a client

Troubleshooting

Common Issues

“Invalid token” or 401 Unauthorized:

  • Verify OAUTH2_ISSUER_URI matches your Keycloak realm URL exactly
  • Check that the token hasn't expired
  • Ensure Keycloak is accessible from the MCP server

“Unable to resolve issuer”:

  • Keycloak must be running and accessible
  • Check the issuer URL: http://localhost:8180/realms/solr-mcp/.well-known/openid-configuration

CORS errors with MCP Inspector:

  • Ensure Web origins are configured in your Keycloak client
  • Add http://localhost:6274 to Web origins

Token doesn't contain roles:

  • Verify the user has roles assigned in Keycloak
  • Check the token contents at https://jwt.io
  • Ensure you're reading the correct claim path (realm_access.roles)

Useful Commands

Inspect a JWT token:

# Decode token (without verification)
echo $TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null | jq

Check Keycloak OpenID configuration:

curl http://localhost:8180/realms/solr-mcp/.well-known/openid-configuration | jq

View Keycloak logs:

docker logs -f keycloak

References