1. IGA và Workflows — Tổng quan
Identity Governance and Administration (IGA) là lĩnh vực quản trị vòng đời identity — từ khi user gia nhập tổ chức (joiner), thay đổi vai trò (mover), đến khi rời đi (leaver). Keycloak Workflows là tính năng preview cho phép tự động hóa các quy trình IGA này.
1.1 Tại sao cần Workflows?
| Vấn đề | Không có Workflows | Có Workflows |
|---|---|---|
| Onboarding nhân viên mới | Admin phải manually tạo account, gán roles, thêm vào groups | Tự động: tạo account → gán roles theo department → thêm groups → gửi welcome email |
| Chuyển phòng ban | Admin phải manually xóa roles cũ, gán roles mới | Tự động detect thay đổi department → update roles/groups tương ứng |
| Offboarding | Quên revoke access → security risk | Tự động disable account → revoke sessions → remove from groups |
| Access review | Không thể review định kỳ | Tự động kiểm tra và báo cáo unused permissions |
1.2 Keycloak Workflows là gì?
Keycloak Workflows là hệ thống event-driven, condition-based cho phép định nghĩa các quy trình tự động gồm:
- Trigger: Sự kiện kích hoạt workflow (user created, attribute changed, login event...)
- Conditions: Điều kiện phải thỏa mãn để workflow thực thi
- Steps: Các hành động thực hiện khi conditions matched (assign role, add to group, send notification...)
2. Bật Workflows (Preview Feature)
Workflows là tính năng preview, cần bật explicitly:
# Bật qua command line
bin/kc.sh start --features=workflows
# Hoặc trong keycloak.conf
features=workflows
# Bật cùng các features khác
bin/kc.sh start --features=workflows,organizations,scripts
# Docker
docker run -e KC_FEATURES=workflows \
quay.io/keycloak/keycloak:latest start-dev
Lưu ý: Preview features có thể thay đổi API giữa các phiên bản. Không nên dùng trong production mà không test kỹ.
3. Workflow Concepts
3.1 Workflow Components
Workflow Definition
├── Metadata (name, description, enabled)
├── Trigger (event type)
├── Conditions (when to execute)
│ ├── Condition 1 (attribute check)
│ ├── Condition 2 (group membership)
│ └── ... (AND/OR logic)
└── Steps (what to do)
├── Step 1 (assign role)
├── Step 2 (add to group)
├── Step 3 (send notification)
└── ... (sequential execution)
3.2 Trigger Types
| Trigger | Mô tả | Use case |
|---|---|---|
| USER_CREATED | User mới được tạo | Onboarding: auto-assign roles/groups |
| USER_UPDATED | User attributes thay đổi | Mover: detect department change |
| USER_DELETED | User bị xóa | Cleanup: revoke external access |
| USER_DISABLED | User bị disable | Offboarding: revoke sessions |
| GROUP_MEMBERSHIP_CHANGED | User thêm/xóa khỏi group | Auto-assign related roles |
| ROLE_ASSIGNED | User được gán role | Cascading permissions |
| ROLE_REMOVED | User bị xóa role | Revoke dependent access |
4. Workflow Definitions
4.1 Cấu trúc Workflow Definition
{
"name": "Employee Onboarding",
"description": "Automatically provision new employees based on department",
"enabled": true,
"trigger": {
"type": "USER_CREATED"
},
"conditions": [
{
"type": "user_attribute",
"attribute": "employeeType",
"operator": "equals",
"value": "full-time"
}
],
"steps": [
{
"type": "assign_role",
"role": "employee"
},
{
"type": "add_to_group",
"group": "/Company/All-Employees"
},
{
"type": "conditional",
"condition": {
"type": "user_attribute",
"attribute": "department",
"operator": "equals",
"value": "engineering"
},
"thenSteps": [
{
"type": "assign_role",
"role": "developer"
},
{
"type": "add_to_group",
"group": "/Company/Engineering"
}
]
}
]
}
4.2 YAML Format
# workflow-onboarding.yaml
name: Employee Onboarding
description: Auto-provision new employees
enabled: true
trigger:
type: USER_CREATED
conditions:
- type: user_attribute
attribute: employeeType
operator: equals
value: full-time
steps:
- type: assign_role
role: employee
- type: add_to_group
group: /Company/All-Employees
- type: conditional
condition:
type: user_attribute
attribute: department
operator: equals
value: engineering
thenSteps:
- type: assign_role
role: developer
- type: add_to_group
group: /Company/Engineering
- type: conditional
condition:
type: user_attribute
attribute: department
operator: equals
value: marketing
thenSteps:
- type: assign_role
role: marketer
- type: add_to_group
group: /Company/Marketing
5. Workflow Expression Language
Workflows sử dụng expression language để định nghĩa conditions động và tham chiếu user attributes.
5.1 Variables
| Variable | Mô tả | Ví dụ |
|---|---|---|
user.username | Username của user | john.doe |
user.email | Email address | [email protected] |
user.firstName | First name | John |
user.lastName | Last name | Doe |
user.attributes.{name} | Custom attribute | user.attributes.department |
user.groups | List group paths | ["/Engineering", "/VPN-Users"] |
user.roles | List assigned roles | ["employee", "developer"] |
event.type | Event type | USER_CREATED |
event.time | Event timestamp | 2026-03-30T10:00:00Z |
5.2 Operators
| Operator | Mô tả | Ví dụ |
|---|---|---|
equals | So sánh bằng | user.attributes.department equals "engineering" |
not_equals | Không bằng | user.attributes.status not_equals "inactive" |
contains | Chứa giá trị | user.email contains "@acme.com" |
starts_with | Bắt đầu bằng | user.username starts_with "svc-" |
ends_with | Kết thúc bằng | user.email ends_with "@acme.com" |
in | Thuộc danh sách | user.attributes.location in ["HCM", "HN", "DN"] |
exists | Attribute tồn tại | user.attributes.employeeId exists |
not_exists | Attribute không tồn tại | user.attributes.termination_date not_exists |
5.3 Functions
# String functions
upper(user.attributes.department) → "ENGINEERING"
lower(user.email) → "[email protected]"
trim(user.attributes.title) → "Senior Developer"
# Date functions
now() → current timestamp
daysAgo(30) → timestamp 30 ngày trước
daysBetween(user.createdTimestamp, now()) → số ngày từ lúc tạo
# Collection functions
size(user.groups) → số groups
contains(user.roles, "admin") → true/false
6. Managing Workflows
6.1 CRUD Operations
# Tạo workflow
curl -X POST "http://localhost:8080/admin/realms/my-realm/workflows" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Employee Onboarding",
"description": "Auto-provision new employees",
"enabled": true,
"trigger": { "type": "USER_CREATED" },
"conditions": [
{
"type": "user_attribute",
"attribute": "employeeType",
"operator": "equals",
"value": "full-time"
}
],
"steps": [
{ "type": "assign_role", "role": "employee" },
{ "type": "add_to_group", "group": "/All-Employees" }
]
}'
# List workflows
curl -s "http://localhost:8080/admin/realms/my-realm/workflows" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" | jq '.'
# Get workflow by ID
WORKFLOW_ID="workflow-uuid"
curl -s "http://localhost:8080/admin/realms/my-realm/workflows/${WORKFLOW_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" | jq '.'
# Update workflow
curl -X PUT "http://localhost:8080/admin/realms/my-realm/workflows/${WORKFLOW_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Employee Onboarding v2",
"enabled": true,
"steps": [
{ "type": "assign_role", "role": "employee" },
{ "type": "add_to_group", "group": "/All-Employees" },
{ "type": "invoke_api", "url": "https://hr.example.com/api/provisioned", "method": "POST" }
]
}'
# Delete workflow
curl -X DELETE "http://localhost:8080/admin/realms/my-realm/workflows/${WORKFLOW_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"
# Enable/Disable workflow
curl -X PUT "http://localhost:8080/admin/realms/my-realm/workflows/${WORKFLOW_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{ "enabled": false }'
7. Defining Conditions
7.1 User Attribute Conditions
{
"conditions": [
{
"type": "user_attribute",
"attribute": "department",
"operator": "equals",
"value": "engineering"
},
{
"type": "user_attribute",
"attribute": "employeeType",
"operator": "in",
"values": ["full-time", "contract"]
}
]
}
7.2 Group Membership Conditions
{
"conditions": [
{
"type": "group_membership",
"group": "/Engineering",
"operator": "is_member"
}
]
}
7.3 Time-based Conditions
{
"conditions": [
{
"type": "time",
"attribute": "user.createdTimestamp",
"operator": "older_than_days",
"value": 90
}
]
}
7.4 Event-based Conditions
{
"conditions": [
{
"type": "event",
"attribute": "event.details.updated_attribute",
"operator": "equals",
"value": "department"
}
]
}
8. Defining Steps
8.1 Assign Role
{
"type": "assign_role",
"role": "employee",
"scope": "realm"
}
// Client role
{
"type": "assign_role",
"role": "editor",
"scope": "client",
"clientId": "my-app"
}
8.2 Remove Role
{
"type": "remove_role",
"role": "contractor",
"scope": "realm"
}
8.3 Add to Group
{
"type": "add_to_group",
"group": "/Company/Engineering/Backend"
}
8.4 Remove from Group
{
"type": "remove_from_group",
"group": "/Company/Marketing"
}
8.5 Send Notification
{
"type": "send_email",
"to": "${user.email}",
"template": "welcome-employee",
"params": {
"name": "${user.firstName}",
"department": "${user.attributes.department}",
"manager": "${user.attributes.manager}"
}
}
8.6 Invoke External API
{
"type": "invoke_api",
"url": "https://hr-system.example.com/api/v1/employees/provisioned",
"method": "POST",
"headers": {
"Content-Type": "application/json",
"X-API-Key": "${env.HR_API_KEY}"
},
"body": {
"employeeId": "${user.attributes.employeeId}",
"email": "${user.email}",
"department": "${user.attributes.department}",
"provisionedAt": "${event.time}"
}
}
8.7 Set User Attribute
{
"type": "set_attribute",
"attribute": "provisionedAt",
"value": "${now()}"
}
{
"type": "set_attribute",
"attribute": "accessLevel",
"value": "standard"
}
9. Joiner-Mover-Leaver (JML) Processes
9.1 Joiner — Automated Onboarding
# workflow-joiner.yaml
name: "JML: Joiner - Employee Onboarding"
description: Auto-provision new full-time employees
enabled: true
trigger:
type: USER_CREATED
conditions:
- type: user_attribute
attribute: employeeType
operator: equals
value: full-time
steps:
# Base provisioning cho tất cả employees
- type: assign_role
role: employee
- type: add_to_group
group: /Company/All-Employees
- type: set_attribute
attribute: onboardingStatus
value: completed
- type: set_attribute
attribute: onboardedAt
value: "${now()}"
# Department-specific provisioning
- type: conditional
condition:
type: user_attribute
attribute: department
operator: equals
value: engineering
thenSteps:
- type: assign_role
role: developer
- type: add_to_group
group: /Company/Engineering
- type: assign_role
role: gitlab-user
scope: client
clientId: gitlab
- type: conditional
condition:
type: user_attribute
attribute: department
operator: equals
value: sales
thenSteps:
- type: assign_role
role: sales-rep
- type: add_to_group
group: /Company/Sales
- type: assign_role
role: crm-user
scope: client
clientId: salesforce
# Notify HR system
- type: invoke_api
url: https://hr.example.com/api/onboarding/completed
method: POST
body:
employeeId: "${user.attributes.employeeId}"
email: "${user.email}"
# Send welcome email
- type: send_email
to: "${user.email}"
template: welcome-employee
9.2 Mover — Department Transfer
# workflow-mover.yaml
name: "JML: Mover - Department Transfer"
description: Handle department changes
enabled: true
trigger:
type: USER_UPDATED
conditions:
- type: event
attribute: event.details.updated_attribute
operator: equals
value: department
steps:
# Lấy old department từ event details
# Remove old department group
- type: remove_from_group
group: "/Company/${event.details.previous_value}"
# Add to new department group
- type: add_to_group
group: "/Company/${user.attributes.department}"
# Log transfer
- type: set_attribute
attribute: lastTransferDate
value: "${now()}"
- type: set_attribute
attribute: previousDepartment
value: "${event.details.previous_value}"
# Notify managers
- type: invoke_api
url: https://hr.example.com/api/transfers
method: POST
body:
employeeId: "${user.attributes.employeeId}"
fromDepartment: "${event.details.previous_value}"
toDepartment: "${user.attributes.department}"
transferDate: "${event.time}"
9.3 Leaver — Automated Offboarding
# workflow-leaver.yaml
name: "JML: Leaver - Employee Offboarding"
description: Auto-deprovision disabled/deleted employees
enabled: true
trigger:
type: USER_DISABLED
conditions:
- type: user_attribute
attribute: employeeType
operator: in
values:
- full-time
- contract
steps:
# Revoke all active sessions
- type: invoke_api
url: "http://localhost:8080/admin/realms/my-realm/users/${user.id}/logout"
method: POST
headers:
Authorization: "Bearer ${admin.token}"
# Remove from all groups (except audit trail groups)
- type: remove_from_group
group: /Company/All-Employees
# Remove sensitive roles
- type: remove_role
role: developer
- type: remove_role
role: admin
# Set offboarding metadata
- type: set_attribute
attribute: offboardingStatus
value: completed
- type: set_attribute
attribute: offboardedAt
value: "${now()}"
- type: set_attribute
attribute: accountDisabledReason
value: employee-departure
# Notify IT and HR
- type: invoke_api
url: https://hr.example.com/api/offboarding/completed
method: POST
body:
employeeId: "${user.attributes.employeeId}"
email: "${user.email}"
offboardedAt: "${event.time}"
# Notify admin via email
- type: send_email
to: [email protected]
template: offboarding-notification
params:
employeeName: "${user.firstName} ${user.lastName}"
department: "${user.attributes.department}"
10. Access Review Workflows
Access reviews cho phép kiểm tra định kỳ xem users có còn cần các quyền hiện tại không:
# workflow-access-review.yaml
name: "Access Review: Inactive Users"
description: Review and flag inactive users
enabled: true
trigger:
type: SCHEDULED
schedule: "0 0 1 * *" # Chạy hàng tháng
conditions:
- type: user_attribute
attribute: lastLoginTimestamp
operator: older_than_days
value: 90
steps:
# Flag user for review
- type: set_attribute
attribute: accessReviewStatus
value: pending-review
- type: set_attribute
attribute: accessReviewDate
value: "${now()}"
# Add to review group
- type: add_to_group
group: /Access-Review/Pending
# Notify user's manager
- type: send_email
to: "${user.attributes.managerEmail}"
template: access-review-notification
params:
employeeName: "${user.firstName} ${user.lastName}"
lastLogin: "${user.attributes.lastLoginTimestamp}"
reviewDeadline: "${daysFromNow(14)}"
# If user has sensitive roles, escalate
- type: conditional
condition:
type: role_assigned
role: admin
thenSteps:
- type: send_email
to: [email protected]
template: access-review-escalation
params:
employeeName: "${user.firstName} ${user.lastName}"
sensitiveRoles: "${user.roles}"
11. Common Enterprise Use Cases
11.1 Contractor Lifecycle Management
# Tự động disable contractor khi hết hợp đồng
name: "Contractor: Auto-disable on contract end"
enabled: true
trigger:
type: SCHEDULED
schedule: "0 0 * * *" # Daily check
conditions:
- type: user_attribute
attribute: employeeType
operator: equals
value: contract
- type: user_attribute
attribute: contractEndDate
operator: before
value: "${now()}"
steps:
- type: disable_user
- type: set_attribute
attribute: disabledReason
value: contract-expired
- type: send_email
to: "${user.attributes.managerEmail}"
template: contractor-expired
11.2 Auto-provisioning from LDAP Sync
# Khi user được sync từ LDAP, auto-assign roles
name: "LDAP: Post-sync provisioning"
enabled: true
trigger:
type: USER_CREATED
conditions:
- type: user_attribute
attribute: LDAP_ID
operator: exists
- type: user_attribute
attribute: department
operator: exists
steps:
- type: assign_role
role: ldap-user
- type: conditional
condition:
type: user_attribute
attribute: memberOf
operator: contains
value: "CN=VPN-Users"
thenSteps:
- type: assign_role
role: vpn-access
11.3 Compliance — Password Rotation Reminder
# Nhắc nhở user đổi password sau 90 ngày
name: "Compliance: Password rotation reminder"
enabled: true
trigger:
type: SCHEDULED
schedule: "0 8 * * 1" # Weekly, Monday 8 AM
conditions:
- type: user_attribute
attribute: lastPasswordChange
operator: older_than_days
value: 80
steps:
- type: send_email
to: "${user.email}"
template: password-rotation-reminder
params:
daysRemaining: "${90 - daysBetween(user.attributes.lastPasswordChange, now())}"
- type: conditional
condition:
type: user_attribute
attribute: lastPasswordChange
operator: older_than_days
value: 90
thenSteps:
- type: set_attribute
attribute: passwordExpired
value: "true"
- type: invoke_api
url: "http://localhost:8080/admin/realms/my-realm/users/${user.id}/execute-actions-email"
method: PUT
body: ["UPDATE_PASSWORD"]
12. Monitoring và Troubleshooting
12.1 Workflow Execution Logs
# Bật debug logging cho workflows
bin/kc.sh start \
--features=workflows \
--log-level=org.keycloak.workflow:DEBUG
# Xem workflow execution history
curl -s "http://localhost:8080/admin/realms/my-realm/workflows/${WORKFLOW_ID}/executions" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" | jq '.'
# Response mẫu:
{
"executions": [
{
"id": "exec-uuid-1",
"workflowId": "workflow-uuid",
"userId": "user-uuid",
"status": "COMPLETED",
"triggeredAt": "2026-03-30T10:00:00Z",
"completedAt": "2026-03-30T10:00:02Z",
"steps": [
{ "type": "assign_role", "status": "SUCCESS" },
{ "type": "add_to_group", "status": "SUCCESS" },
{ "type": "send_email", "status": "SUCCESS" }
]
}
]
}
12.2 Common Issues
| Issue | Nguyên nhân | Giải pháp |
|---|---|---|
| Workflow không trigger | Feature chưa bật hoặc workflow disabled | Kiểm tra --features=workflows và enabled: true |
| Condition không match | Attribute name hoặc value sai | Verify user attributes qua Admin Console |
| Step failed | Role/Group không tồn tại | Tạo role/group trước khi reference trong workflow |
| API call failed | External service không available | Kiểm tra network connectivity, add retry logic |
| Email không gửi | SMTP chưa cấu hình | Cấu hình Realm Settings → Email |
13. Best Practices
- Bắt đầu đơn giản — implement JML cơ bản trước, sau đó thêm complexity
- Test workflows kỹ trước production — đây là preview feature, có thể có bugs
- Idempotent steps — đảm bảo steps có thể chạy lại mà không gây side effects
- Error handling — plan cho trường hợp external API fail hoặc email bounce
- Audit trail — luôn set attributes ghi lại workflow execution (timestamps, status)
- Separation of concerns — tách JML thành workflows riêng biệt (joiner, mover, leaver)
- Version control — export workflow definitions vào git, dùng CI/CD để deploy
- Monitor execution logs — set up alerting cho failed workflow executions
- Gradual rollout — bật workflows cho nhóm nhỏ users trước, sau đó mở rộng