keycloak-admin

Comprehensive Keycloak administration for the keycloak-alpha multi-tenant MERN platform with OAuth 2.0 Authorization Code Flow.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "keycloak-admin" with this command: npx skills add lobbi-docs/claude/lobbi-docs-claude-keycloak-admin

Keycloak Admin Skill

Comprehensive Keycloak administration for the keycloak-alpha multi-tenant MERN platform with OAuth 2.0 Authorization Code Flow.

When to Use This Skill

Activate this skill when:

  • Setting up Keycloak realms and clients

  • Configuring OAuth 2.0 Authorization Code Flow

  • Managing users with custom attributes (org_id)

  • Deploying custom themes

  • Troubleshooting authentication issues

  • Configuring token lifetimes and session management

Keycloak Admin REST API

Authentication

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"

Key API Endpoints

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

Realm Creation and Configuration

Create a New Realm

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 }'

Configure Realm Settings

// 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 };

Client Configuration for OAuth 2.0 Authorization Code Flow

Create Client

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" ] }'

Client Configuration in keycloak-alpha

// 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 };

Client Secret Management

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"

User Management with Custom Attributes

Create User with org_id

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 }] }'

User Service in keycloak-alpha

// 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; } }

Query Users by org_id

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}"

Role and Group Management

Create Realm Roles

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 }'

Assign Roles to User

// 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 Groups for Organizations

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"

Theme Deployment

Theme Structure

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

Theme Properties

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

Deploy Theme

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" }'

Theme Customization per Organization

// 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

Token Configuration and Session Management

Token Lifetime Configuration

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 }'

Custom Token Mapper for org_id

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" } }'

Verify Token Claims

// 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();

}); }

Common Troubleshooting

Issue: CORS Errors

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": ["+"] }'

Issue: Invalid Redirect URI

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;

Issue: Token Not Including Custom Claims

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": [...] }'

Issue: User Cannot Login

Checklist:

  • Verify user is enabled: GET /admin/realms/lobbi/users/{id}

  • Check email is verified (if required)

  • Verify password is not temporary

  • Check realm login settings allow email login

  • Review authentication flow configuration

Issue: Theme Not Applied

Solution:

  • Verify theme is copied to Keycloak themes directory

  • Restart Keycloak container

  • Clear browser cache

  • Check theme name in realm settings matches theme directory name

File Locations in keycloak-alpha

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

Best Practices

  • Always use PKCE for Authorization Code Flow in SPAs

  • Never expose client secrets in frontend code

  • Validate org_id claim in every backend request

  • Use short access token lifespans (5-15 minutes)

  • Implement refresh token rotation for enhanced security

  • Enable brute force protection in realm settings

  • Use groups for organization-level permissions

  • Version control themes in the repository

  • Test theme changes in development realm first

  • Monitor token usage and session metrics

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

devops practices

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

code-review

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

code quality

No summary provided by upstream source.

Repository SourceNeeds Review