keycloak-admin

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Keycloak Admin Skill

Keycloak管理技能

Comprehensive Keycloak administration for the keycloak-alpha multi-tenant MERN platform with OAuth 2.0 Authorization Code Flow.
适用于keycloak-alpha多租户MERN平台的全功能Keycloak管理方案,支持OAuth 2.0授权码流。

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 realm和客户端
  • 配置OAuth 2.0授权码流
  • 管理带自定义属性(org_id)的用户
  • 部署自定义主题
  • 排查身份认证相关问题
  • 配置令牌有效期和会话管理

Keycloak Admin REST API

Keycloak Admin REST API

Authentication

身份认证

Use the admin-cli client to obtain an access token:
bash
undefined
使用admin-cli客户端获取访问令牌:
bash
undefined

Get admin 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')
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

Use token in subsequent requests

curl -H "Authorization: Bearer $TOKEN"
"http://localhost:8080/admin/realms/master"
undefined
curl -H "Authorization: Bearer $TOKEN"
"http://localhost:8080/admin/realms/master"
undefined

Key API Endpoints

核心API端点

EndpointMethodPurpose
/admin/realms
GETList all realms
/admin/realms/{realm}
POSTCreate realm
/admin/realms/{realm}/clients
GET/POSTManage clients
/admin/realms/{realm}/users
GET/POSTManage users
/admin/realms/{realm}/roles
GET/POSTManage roles
/admin/realms/{realm}/groups
GET/POSTManage groups
端点请求方法用途
/admin/realms
GET列出所有realm
/admin/realms/{realm}
POST创建realm
/admin/realms/{realm}/clients
GET/POST管理客户端
/admin/realms/{realm}/users
GET/POST管理用户
/admin/realms/{realm}/roles
GET/POST管理角色
/admin/realms/{realm}/groups
GET/POST管理用户组

Realm Creation and Configuration

Realm创建与配置

Create a New Realm

创建新Realm

bash
undefined
bash
undefined

Create realm with basic configuration

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

Configure Realm Settings

配置Realm设置

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

OAuth 2.0授权码流的客户端配置

Create Client

创建客户端

bash
undefined
bash
undefined

Create client for Authorization Code Flow

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

Client Configuration in keycloak-alpha

keycloak-alpha中的客户端配置

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

客户端密钥管理

bash
undefined
bash
undefined

Get client secret

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

Regenerate client secret

undefined
undefined

User Management with Custom Attributes

带自定义属性的用户管理

Create User with org_id

创建带org_id的用户

bash
undefined
bash
undefined

Create user with custom org_id attribute

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

User Service in keycloak-alpha

keycloak-alpha中的用户服务

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

按org_id查询用户

bash
undefined
bash
undefined

Search users by org_id attribute

Search users by org_id attribute

Get user with attributes

Get user with attributes

curl -H "Authorization: Bearer $TOKEN"
"http://localhost:8080/admin/realms/lobbi/users/{user-id}"
undefined
curl -H "Authorization: Bearer $TOKEN"
"http://localhost:8080/admin/realms/lobbi/users/{user-id}"
undefined

Role and Group Management

角色与用户组管理

Create Realm Roles

创建Realm角色

bash
undefined
bash
undefined

Create organization-level 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 }'
undefined
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 }'
undefined

Assign Roles to User

为用户分配角色

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

为企业创建用户组

bash
undefined
bash
undefined

Create group for organization

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

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"
undefined
GROUP_ID="..." USER_ID="..." curl -X PUT "http://localhost:8080/admin/realms/lobbi/users/$USER_ID/groups/$GROUP_ID"
-H "Authorization: Bearer $TOKEN"
undefined

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

主题配置

properties
undefined
properties
undefined

themes/lobbi-base/login/theme.properties

themes/lobbi-base/login/theme.properties

parent=keycloak import=common/keycloak
styles=css/login.css
parent=keycloak import=common/keycloak
styles=css/login.css

Localization

Localization

locales=en,es,fr
locales=en,es,fr

Custom properties

Custom properties

logo.url=/resources/img/logo.png
undefined
logo.url=/resources/img/logo.png
undefined

Deploy Theme

部署主题

bash
undefined
bash
undefined

Copy theme to Keycloak

Copy theme to Keycloak

docker cp themes/lobbi-base keycloak:/opt/keycloak/themes/
docker cp themes/lobbi-base keycloak:/opt/keycloak/themes/

Restart Keycloak to pick up new theme

Restart Keycloak to pick up new theme

docker restart keycloak
docker restart keycloak

Set theme for realm

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

Theme Customization per Organization

按企业自定义主题

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

令牌有效期配置

bash
undefined
bash
undefined

Update token lifespans

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

Custom Token Mapper for org_id

org_id的自定义令牌映射

bash
undefined
bash
undefined

Create protocol mapper to include org_id in token

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

Verify Token Claims

校验令牌声明

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

问题:CORS错误

Solution: Configure Web Origins in client settings
bash
curl -X PUT "http://localhost:8080/admin/realms/lobbi/clients/$CLIENT_UUID" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "webOrigins": ["+"]
  }'
解决方案: 在客户端设置中配置Web Origins
bash
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

问题:重定向URI无效

Solution: Verify redirect URIs match exactly
javascript
// 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`;
解决方案: 校验重定向URI完全匹配
javascript
// 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
bash
undefined
解决方案: 校验客户端范围已添加协议映射
bash
undefined

Check client scopes

Check client scopes

Add custom scope with org_id mapper

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

Issue: User Cannot Login

问题:用户无法登录

Checklist:
  1. Verify user is enabled:
    GET /admin/realms/lobbi/users/{id}
  2. Check email is verified (if required)
  3. Verify password is not temporary
  4. Check realm login settings allow email login
  5. Review authentication flow configuration
检查清单:
  1. 验证用户已启用:
    GET /admin/realms/lobbi/users/{id}
  2. 检查邮箱是否已验证(如果要求验证)
  3. 验证密码不是临时密码
  4. 检查realm登录设置是否允许邮箱登录
  5. 检查身份认证流配置

Issue: Theme Not Applied

问题:主题未生效

Solution:
  1. Verify theme is copied to Keycloak themes directory
  2. Restart Keycloak container
  3. Clear browser cache
  4. Check theme name in realm settings matches theme directory name
解决方案:
  1. 验证主题已复制到Keycloak的themes目录
  2. 重启Keycloak容器
  3. 清除浏览器缓存
  4. 检查realm设置中的主题名称与主题目录名称一致

File Locations in keycloak-alpha

keycloak-alpha中的文件位置

PathPurpose
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
路径用途
services/keycloak-service/
Keycloak配置与主题
services/user-service/
用户管理API
services/api-gateway/src/middleware/auth.middleware.js
令牌校验
apps/web-app/src/config/keycloak.config.js
前端Keycloak配置
apps/web-app/src/hooks/useAuth.js
身份认证钩子

Best Practices

最佳实践

  1. Always use PKCE for Authorization Code Flow in SPAs
  2. Never expose client secrets in frontend code
  3. Validate org_id claim in every backend request
  4. Use short access token lifespans (5-15 minutes)
  5. Implement refresh token rotation for enhanced security
  6. Enable brute force protection in realm settings
  7. Use groups for organization-level permissions
  8. Version control themes in the repository
  9. Test theme changes in development realm first
  10. Monitor token usage and session metrics
  1. 单页应用的授权码流必须使用PKCE
  2. 永远不要在前端代码中暴露客户端密钥
  3. 每个后端请求都要校验org_id声明
  4. 使用较短的访问令牌有效期(5-15分钟)
  5. 实现刷新令牌轮换提升安全性
  6. 在realm设置中启用暴力破解防护
  7. 使用用户组管理企业级权限
  8. 主题文件纳入版本控制
  9. 先在开发环境realm测试主题变更
  10. 监控令牌使用和会话指标