Keycloak administration including realm management, client configuration, OAuth 2.0 setup, user management with custom attributes, role and group management, theme deployment, and token configuration. Activate for Keycloak Admin API operations, authentication setup, and identity provider configuration.
/plugin marketplace add Lobbi-Docs/claude/plugin install lobbi-platform-manager@claude-orchestrationThis skill is limited to using the following tools:
Comprehensive Keycloak administration for the keycloak-alpha multi-tenant MERN platform with OAuth 2.0 Authorization Code Flow.
Activate this skill when:
Use the admin-cli client to obtain an access token:
# Get admin access token
TOKEN=$(curl -X POST "http://localhost:8080/realms/master/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=admin" \
-d "password=admin" \
-d "grant_type=password" \
-d "client_id=admin-cli" | jq -r '.access_token')
# Use token in subsequent requests
curl -H "Authorization: Bearer $TOKEN" \
"http://localhost:8080/admin/realms/master"
| Endpoint | Method | Purpose |
|---|---|---|
/admin/realms | GET | List all realms |
/admin/realms/{realm} | POST | Create realm |
/admin/realms/{realm}/clients | GET/POST | Manage clients |
/admin/realms/{realm}/users | GET/POST | Manage users |
/admin/realms/{realm}/roles | GET/POST | Manage roles |
/admin/realms/{realm}/groups | GET/POST | Manage groups |
# Create realm with basic configuration
curl -X POST "http://localhost:8080/admin/realms" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"realm": "lobbi",
"enabled": true,
"displayName": "Lobbi Platform",
"sslRequired": "external",
"registrationAllowed": false,
"loginWithEmailAllowed": true,
"duplicateEmailsAllowed": false,
"resetPasswordAllowed": true,
"editUsernameAllowed": false,
"bruteForceProtected": true,
"permanentLockout": false,
"maxFailureWaitSeconds": 900,
"minimumQuickLoginWaitSeconds": 60,
"waitIncrementSeconds": 60,
"quickLoginCheckMilliSeconds": 1000,
"maxDeltaTimeSeconds": 43200,
"failureFactor": 30,
"defaultSignatureAlgorithm": "RS256",
"revokeRefreshToken": false,
"refreshTokenMaxReuse": 0,
"accessTokenLifespan": 300,
"accessTokenLifespanForImplicitFlow": 900,
"ssoSessionIdleTimeout": 1800,
"ssoSessionMaxLifespan": 36000,
"offlineSessionIdleTimeout": 2592000,
"accessCodeLifespan": 60,
"accessCodeLifespanUserAction": 300,
"accessCodeLifespanLogin": 1800
}'
// In keycloak-alpha: services/keycloak-service/src/config/realm-config.js
export const realmDefaults = {
realm: process.env.KEYCLOAK_REALM || 'lobbi',
enabled: true,
displayName: 'Lobbi Platform',
// Security settings
sslRequired: 'external',
registrationAllowed: false,
loginWithEmailAllowed: true,
duplicateEmailsAllowed: false,
// Token lifespans (seconds)
accessTokenLifespan: 300, // 5 minutes
accessTokenLifespanForImplicitFlow: 900, // 15 minutes
ssoSessionIdleTimeout: 1800, // 30 minutes
ssoSessionMaxLifespan: 36000, // 10 hours
offlineSessionIdleTimeout: 2592000, // 30 days
// Login settings
resetPasswordAllowed: true,
editUsernameAllowed: false,
// Brute force protection
bruteForceProtected: true,
permanentLockout: false,
maxFailureWaitSeconds: 900,
minimumQuickLoginWaitSeconds: 60,
failureFactor: 30
};
# Create client for Authorization Code Flow
curl -X POST "http://localhost:8080/admin/realms/lobbi/clients" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"clientId": "lobbi-web-app",
"name": "Lobbi Web Application",
"enabled": true,
"protocol": "openid-connect",
"publicClient": false,
"standardFlowEnabled": true,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": false,
"serviceAccountsEnabled": false,
"redirectUris": [
"http://localhost:3000/auth/callback",
"https://*.lobbi.com/auth/callback"
],
"webOrigins": [
"http://localhost:3000",
"https://*.lobbi.com"
],
"attributes": {
"pkce.code.challenge.method": "S256"
},
"defaultClientScopes": [
"email",
"profile",
"roles",
"web-origins"
],
"optionalClientScopes": [
"address",
"phone",
"offline_access"
]
}'
// In: apps/web-app/src/config/keycloak.config.js
export const keycloakConfig = {
url: process.env.VITE_KEYCLOAK_URL || 'http://localhost:8080',
realm: process.env.VITE_KEYCLOAK_REALM || 'lobbi',
clientId: process.env.VITE_KEYCLOAK_CLIENT_ID || 'lobbi-web-app',
};
// OAuth 2.0 Authorization Code Flow with PKCE
export const authConfig = {
flow: 'standard',
pkceMethod: 'S256',
responseType: 'code',
scope: 'openid profile email roles',
// Redirect URIs
redirectUri: `${window.location.origin}/auth/callback`,
postLogoutRedirectUri: `${window.location.origin}/`,
// Token handling
checkLoginIframe: true,
checkLoginIframeInterval: 5,
onLoad: 'check-sso',
silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html`
};
# Get client secret
CLIENT_UUID=$(curl -H "Authorization: Bearer $TOKEN" \
"http://localhost:8080/admin/realms/lobbi/clients?clientId=lobbi-web-app" \
| jq -r '.[0].id')
curl -H "Authorization: Bearer $TOKEN" \
"http://localhost:8080/admin/realms/lobbi/clients/$CLIENT_UUID/client-secret" \
| jq -r '.value'
# Regenerate client secret
curl -X POST -H "Authorization: Bearer $TOKEN" \
"http://localhost:8080/admin/realms/lobbi/clients/$CLIENT_UUID/client-secret"
# Create user with custom org_id attribute
curl -X POST "http://localhost:8080/admin/realms/lobbi/users" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "john.doe@acme.com",
"email": "john.doe@acme.com",
"firstName": "John",
"lastName": "Doe",
"enabled": true,
"emailVerified": true,
"attributes": {
"org_id": ["org_acme"],
"tenant_name": ["ACME Corporation"]
},
"credentials": [{
"type": "password",
"value": "temp_password_123",
"temporary": true
}]
}'
// In: services/user-service/src/controllers/user.controller.js
import axios from 'axios';
export class UserController {
async createUser(req, res) {
const { email, firstName, lastName, orgId } = req.body;
// Get admin token
const adminToken = await this.getAdminToken();
// Create user in Keycloak
const userData = {
username: email,
email,
firstName,
lastName,
enabled: true,
emailVerified: false,
attributes: {
org_id: [orgId],
created_by: [req.user.sub]
},
credentials: [{
type: 'password',
value: this.generateTemporaryPassword(),
temporary: true
}]
};
try {
const response = await axios.post(
`${process.env.KEYCLOAK_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users`,
userData,
{ headers: { Authorization: `Bearer ${adminToken}` } }
);
// Extract user ID from Location header
const userId = response.headers.location.split('/').pop();
// Assign default roles
await this.assignRoles(userId, ['user'], adminToken);
// Send verification email
await this.sendVerificationEmail(userId, adminToken);
res.status(201).json({ userId, email });
} catch (error) {
console.error('User creation failed:', error.response?.data);
res.status(500).json({ error: 'Failed to create user' });
}
}
async getAdminToken() {
const response = await axios.post(
`${process.env.KEYCLOAK_URL}/realms/master/protocol/openid-connect/token`,
new URLSearchParams({
username: process.env.KEYCLOAK_ADMIN_USER,
password: process.env.KEYCLOAK_ADMIN_PASSWORD,
grant_type: 'password',
client_id: 'admin-cli'
}),
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
);
return response.data.access_token;
}
}
# Search users by org_id attribute
curl -H "Authorization: Bearer $TOKEN" \
"http://localhost:8080/admin/realms/lobbi/users?q=org_id:org_acme"
# Get user with attributes
curl -H "Authorization: Bearer $TOKEN" \
"http://localhost:8080/admin/realms/lobbi/users/{user-id}"
# Create organization-level roles
curl -X POST "http://localhost:8080/admin/realms/lobbi/roles" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "org_admin",
"description": "Organization Administrator",
"composite": false,
"clientRole": false
}'
curl -X POST "http://localhost:8080/admin/realms/lobbi/roles" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "org_user",
"description": "Organization User",
"composite": false,
"clientRole": false
}'
// In: services/user-service/src/services/role.service.js
export class RoleService {
async assignRolesToUser(userId, roleNames, adminToken) {
// Get role definitions
const roles = await Promise.all(
roleNames.map(async (roleName) => {
const response = await axios.get(
`${process.env.KEYCLOAK_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/roles/${roleName}`,
{ headers: { Authorization: `Bearer ${adminToken}` } }
);
return response.data;
})
);
// Assign roles to user
await axios.post(
`${process.env.KEYCLOAK_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users/${userId}/role-mappings/realm`,
roles,
{ headers: { Authorization: `Bearer ${adminToken}` } }
);
}
async getUserRoles(userId, adminToken) {
const response = await axios.get(
`${process.env.KEYCLOAK_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users/${userId}/role-mappings`,
{ headers: { Authorization: `Bearer ${adminToken}` } }
);
return response.data;
}
}
# Create group for organization
curl -X POST "http://localhost:8080/admin/realms/lobbi/groups" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "org_acme",
"attributes": {
"org_id": ["org_acme"],
"org_name": ["ACME Corporation"]
}
}'
# Add user to group
GROUP_ID="..."
USER_ID="..."
curl -X PUT "http://localhost:8080/admin/realms/lobbi/users/$USER_ID/groups/$GROUP_ID" \
-H "Authorization: Bearer $TOKEN"
keycloak-alpha/
└── services/
└── keycloak-service/
└── themes/
├── lobbi-base/
│ ├── login/
│ │ ├── theme.properties
│ │ ├── login.ftl
│ │ ├── register.ftl
│ │ └── resources/
│ │ ├── css/
│ │ │ └── login.css
│ │ ├── img/
│ │ │ └── logo.png
│ │ └── js/
│ │ └── login.js
│ ├── account/
│ └── email/
└── org-acme/
├── login/
│ ├── theme.properties (parent=lobbi-base)
│ └── resources/
│ ├── css/
│ │ └── custom.css
│ └── img/
│ └── org-logo.png
# themes/lobbi-base/login/theme.properties
parent=keycloak
import=common/keycloak
styles=css/login.css
# Localization
locales=en,es,fr
# Custom properties
logo.url=/resources/img/logo.png
# Copy theme to Keycloak
docker cp themes/lobbi-base keycloak:/opt/keycloak/themes/
# Restart Keycloak to pick up new theme
docker restart keycloak
# Set theme for realm
curl -X PUT "http://localhost:8080/admin/realms/lobbi" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"loginTheme": "lobbi-base",
"accountTheme": "lobbi-base",
"emailTheme": "lobbi-base"
}'
// In: services/keycloak-service/src/middleware/theme-mapper.js
export const themeMapper = {
org_acme: 'org-acme',
org_beta: 'org-beta',
default: 'lobbi-base'
};
export function getThemeForOrg(orgId) {
return themeMapper[orgId] || themeMapper.default;
}
// Apply theme dynamically via query parameter
// URL: http://localhost:8080/realms/lobbi/protocol/openid-connect/auth?kc_theme=org-acme
# Update token lifespans
curl -X PUT "http://localhost:8080/admin/realms/lobbi" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"accessTokenLifespan": 300,
"accessTokenLifespanForImplicitFlow": 900,
"ssoSessionIdleTimeout": 1800,
"ssoSessionMaxLifespan": 36000,
"offlineSessionIdleTimeout": 2592000,
"accessCodeLifespan": 60,
"accessCodeLifespanUserAction": 300
}'
# Create protocol mapper to include org_id in token
CLIENT_UUID="..."
curl -X POST "http://localhost:8080/admin/realms/lobbi/clients/$CLIENT_UUID/protocol-mappers/models" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "org_id",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"config": {
"user.attribute": "org_id",
"claim.name": "org_id",
"jsonType.label": "String",
"id.token.claim": "true",
"access.token.claim": "true",
"userinfo.token.claim": "true"
}
}'
// In: services/api-gateway/src/middleware/auth.middleware.js
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
const client = jwksClient({
jwksUri: `${process.env.KEYCLOAK_URL}/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/certs`
});
function getKey(header, callback) {
client.getSigningKey(header.kid, (err, key) => {
const signingKey = key.publicKey || key.rsaPublicKey;
callback(null, signingKey);
});
}
export async function verifyToken(req, res, next) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
jwt.verify(token, getKey, {
audience: 'account',
issuer: `${process.env.KEYCLOAK_URL}/realms/${process.env.KEYCLOAK_REALM}`,
algorithms: ['RS256']
}, (err, decoded) => {
if (err) {
return res.status(401).json({ error: 'Invalid token' });
}
// Verify org_id claim exists
if (!decoded.org_id) {
return res.status(403).json({ error: 'Missing org_id claim' });
}
req.user = decoded;
next();
});
}
Solution: Configure Web Origins in client settings
curl -X PUT "http://localhost:8080/admin/realms/lobbi/clients/$CLIENT_UUID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"webOrigins": ["+"]
}'
Solution: Verify redirect URIs match exactly
// Check configured URIs
const redirectUris = [
'http://localhost:3000/auth/callback',
'https://app.lobbi.com/auth/callback'
];
// Ensure callback URL matches
const callbackUrl = `${window.location.origin}/auth/callback`;
Solution: Verify protocol mapper is added to client scopes
# Check client scopes
curl -H "Authorization: Bearer $TOKEN" \
"http://localhost:8080/admin/realms/lobbi/clients/$CLIENT_UUID/default-client-scopes"
# Add custom scope with org_id mapper
curl -X POST "http://localhost:8080/admin/realms/lobbi/client-scopes" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "org-scope",
"protocol": "openid-connect",
"protocolMappers": [...]
}'
Checklist:
GET /admin/realms/lobbi/users/{id}Solution:
| Path | Purpose |
|---|---|
services/keycloak-service/ | Keycloak configuration and themes |
services/user-service/ | User management API |
services/api-gateway/src/middleware/auth.middleware.js | Token verification |
apps/web-app/src/config/keycloak.config.js | Frontend Keycloak config |
apps/web-app/src/hooks/useAuth.js | Authentication hooks |
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.