1. Organizations — Tổng quan
Keycloak Organizations là tính năng cho phép quản lý multi-tenancy trong một realm duy nhất. Thay vì tạo nhiều realms cho từng tổ chức (tenant), bạn có thể tạo Organizations bên trong một realm để nhóm users, quản lý domains, và kiểm soát truy cập theo tổ chức.
Đây là tính năng quan trọng cho các nền tảng B2B (Business-to-Business) và B2B2C (Business-to-Business-to-Consumer), còn gọi là CIAM (Customer Identity and Access Management).
1.1 Use Cases
| Scenario | Mô tả |
|---|---|
| SaaS Multi-tenant | Mỗi công ty khách hàng là một organization, users thuộc về organization của họ |
| B2B Portal | Đối tác/vendor có organization riêng, nhân viên đối tác truy cập portal qua organization |
| Enterprise with subsidiaries | Tập đoàn có nhiều công ty con, mỗi công ty con là một organization |
| Educational platform | Mỗi trường/viện là một organization, giáo viên/sinh viên là members |
2. Bật Organizations Feature
Organizations là tính năng có sẵn trong Keycloak 25+. Để bật:
2.1 Bật trên Realm
# Qua Admin Console:
# Realm Settings → General → Organizations → Enabled
# Qua kcadm.sh:
kcadm.sh update realms/my-realm \
-s organizationsEnabled=true
2.2 Kiểm tra trạng thái
# Qua REST API
curl -s "http://localhost:8080/admin/realms/my-realm" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" | jq '.organizationsEnabled'
# Output: true
3. Tạo và quản lý Organizations
3.1 Tạo Organization qua Admin Console
Vào Admin Console → Organizations → Create organization:
| Field | Mô tả | Ví dụ |
|---|---|---|
| Name | Tên organization (bắt buộc) | Acme Corporation |
| Alias | Alias duy nhất, tự sinh từ name | acme-corporation |
| Description | Mô tả tổ chức | Acme Corp - Enterprise customer |
| Redirect URL | URL redirect sau khi member đăng nhập | https://app.acme.com |
3.2 Tạo Organization qua REST API
# Tạo organization
curl -X POST "http://localhost:8080/admin/realms/my-realm/organizations" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Corporation",
"alias": "acme-corporation",
"description": "Enterprise customer - Acme Corp",
"redirectUrl": "https://app.acme.com",
"enabled": true,
"domains": [
{
"name": "acme.com",
"verified": true
}
],
"attributes": {
"plan": ["enterprise"],
"industry": ["technology"],
"region": ["asia-pacific"]
}
}'
# List organizations
curl -s "http://localhost:8080/admin/realms/my-realm/organizations" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" | jq '.'
# Get organization by ID
ORG_ID="org-uuid-here"
curl -s "http://localhost:8080/admin/realms/my-realm/organizations/${ORG_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" | jq '.'
# Update organization
curl -X PUT "http://localhost:8080/admin/realms/my-realm/organizations/${ORG_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Corporation (Updated)",
"description": "Updated description",
"enabled": true
}'
# Delete organization
curl -X DELETE "http://localhost:8080/admin/realms/my-realm/organizations/${ORG_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"
4. Organization Domains
Organization domains cho phép tự động liên kết users với organization dựa trên email domain. Khi user đăng ký hoặc đăng nhập với email thuộc domain đã đăng ký, Keycloak có thể tự động gắn user vào organization tương ứng.
# Thêm domain cho organization
curl -X POST "http://localhost:8080/admin/realms/my-realm/organizations/${ORG_ID}/domains" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "acme.com",
"verified": true
}'
# List domains
curl -s "http://localhost:8080/admin/realms/my-realm/organizations/${ORG_ID}/domains" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" | jq '.'
Domain states:
| State | Mô tả |
|---|---|
| Verified | Domain đã xác minh — users với email domain này tự động qualified cho organization |
| Unverified | Domain chưa xác minh — chỉ dùng cho matching, cần admin approve |
Lưu ý quan trọng:
- Một domain chỉ thuộc về một organization duy nhất
- Domain verification giúp đảm bảo organization thực sự sở hữu domain đó
- Subdomain matching:
acme.comsẽ match[email protected]nhưng không match[email protected]
5. Organization Attributes
Custom attributes cho phép lưu metadata bổ sung cho organizations:
# Set attributes khi tạo hoặc update organization
curl -X PUT "http://localhost:8080/admin/realms/my-realm/organizations/${ORG_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Corporation",
"attributes": {
"plan": ["enterprise"],
"max_users": ["500"],
"contract_end_date": ["2027-12-31"],
"sla_tier": ["platinum"],
"billing_email": ["[email protected]"]
}
}'
Attributes có thể được sử dụng trong:
- Token claims — thêm organization metadata vào access/ID tokens
- Authorization policies — phân quyền dựa trên attributes
- Custom logic — xử lý business logic trong application
6. Quản lý Members
6.1 Managed vs Unmanaged Members
| Type | Mô tả | Ví dụ |
|---|---|---|
| Managed | User được organization quản lý hoàn toàn — lifecycle bị ràng buộc với organization | Nhân viên công ty: khi rời organization, tài khoản bị vô hiệu hóa |
| Unmanaged | User tự do, chỉ "tham gia" organization — tài khoản tồn tại độc lập | Freelancer, contractor: có thể thuộc nhiều organizations |
6.2 Thêm existing users vào Organization
# Thêm user vào organization
USER_ID="user-uuid-here"
curl -X PUT "http://localhost:8080/admin/realms/my-realm/organizations/${ORG_ID}/members/${USER_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"
# List members của organization
curl -s "http://localhost:8080/admin/realms/my-realm/organizations/${ORG_ID}/members" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" | jq '.[].username'
# Get membership info cho user
curl -s "http://localhost:8080/admin/realms/my-realm/organizations/${ORG_ID}/members/${USER_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" | jq '.'
# Remove member
curl -X DELETE "http://localhost:8080/admin/realms/my-realm/organizations/${ORG_ID}/members/${USER_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"
6.3 Member Roles
Organization members có thể được gán roles trong context của organization:
| Role | Mô tả |
|---|---|
| member | Role mặc định — standard access |
| admin | Organization admin — có thể quản lý members và settings |
7. Invitation Management
Keycloak Organizations hỗ trợ mời users tham gia organization qua email invitations.
7.1 Gửi Invitation
# Gửi invitation qua REST API
curl -X POST "http://localhost:8080/admin/realms/my-realm/organizations/${ORG_ID}/invitations" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"firstName": "John",
"lastName": "Doe",
"redirectUrl": "https://app.example.com/welcome"
}'
7.2 Invitation States
| State | Mô tả |
|---|---|
| Pending | Invitation đã gửi, chưa được accept |
| Expired | Invitation hết hạn (configurable expiration) |
7.3 Quản lý Invitations
# List pending invitations
curl -s "http://localhost:8080/admin/realms/my-realm/organizations/${ORG_ID}/invitations" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" | jq '.'
# Resend invitation
INV_ID="invitation-uuid-here"
curl -X POST "http://localhost:8080/admin/realms/my-realm/organizations/${ORG_ID}/invitations/${INV_ID}/resend" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"
# Delete/cancel invitation
curl -X DELETE "http://localhost:8080/admin/realms/my-realm/organizations/${ORG_ID}/invitations/${INV_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"
Email template: Keycloak gửi email sử dụng template có thể customize tại Realm Settings → Email → Templates. Template mặc định bao gồm link để user accept invitation và tạo tài khoản (nếu chưa có).
8. Liên kết Identity Providers với Organizations
Mỗi organization có thể có Identity Provider riêng, cho phép members đăng nhập qua IdP của tổ chức họ (ví dụ: Acme Corp dùng Google Workspace, Beta Inc dùng Okta).
8.1 Liên kết IdP với Organization
# Liên kết Identity Provider với organization
IDP_ALIAS="acme-google-workspace"
curl -X POST "http://localhost:8080/admin/realms/my-realm/organizations/${ORG_ID}/identity-providers" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d "\"${IDP_ALIAS}\""
# List linked Identity Providers
curl -s "http://localhost:8080/admin/realms/my-realm/organizations/${ORG_ID}/identity-providers" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" | jq '.'
# Unlink Identity Provider
curl -X DELETE "http://localhost:8080/admin/realms/my-realm/organizations/${ORG_ID}/identity-providers/${IDP_ALIAS}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"
8.2 Cách hoạt động
- User nhập email trên login page (identity-first login)
- Keycloak xác định email domain → tìm organization tương ứng
- Nếu organization có linked IdP → redirect user đến IdP của organization đó
- User authenticate với IdP của tổ chức
- Keycloak nhận response và tự động gắn user vào organization
9. Identity-First Login
Identity-first login là flow mà user nhập email trước, sau đó Keycloak quyết định authentication method phù hợp dựa trên organization membership.
9.1 Cấu hình Identity-First Login
Khi bật Organizations, Keycloak tự động tạo flow "organization" cho browser authentication. Flow này hoạt động như sau:
Organization Browser Flow
├── Cookie (Alternative)
├── Organization Identity-First (Alternative)
│ ├── Username Form (Required) → User nhập email
│ └── Organization (Conditional)
│ ├── Condition - Organization Member → Kiểm tra user thuộc organization
│ └── Organization Identity Provider → Redirect đến org's IdP
└── Forms (Alternative)
├── Username Password Form (Required)
└── Conditional OTP (Conditional)
9.2 Cấu hình existing flow cho Organizations
# Bind Organization flow cho Browser
kcadm.sh update realms/my-realm \
-s 'browserFlow=organization browser'
10. Mapping Organization Claims vào Tokens
Keycloak có thể thêm organization information vào access tokens và ID tokens, cho phép application biết user thuộc organization nào.
10.1 Organization Membership Mapper
Keycloak tự động thêm organization claim khi Organizations feature được bật. Claim format:
{
"sub": "user-uuid",
"email": "[email protected]",
"organization": {
"acme-corporation": {
"name": "Acme Corporation",
"roles": ["member"]
}
},
"iss": "http://localhost:8080/realms/my-realm",
"aud": "my-app"
}
10.2 Cấu hình Mapper tùy chỉnh
Bạn có thể thêm Organization Membership Protocol Mapper vào client scope để customize claim:
# Thêm Organization Membership Mapper vào client scope
kcadm.sh create clients/${CLIENT_ID}/protocol-mappers/models -r my-realm \
-s name="organization-membership" \
-s protocol=openid-connect \
-s protocolMapper=oidc-organization-membership-mapper \
-s 'config."claim.name"=organization' \
-s 'config."id.token.claim"=true' \
-s 'config."access.token.claim"=true' \
-s 'config."userinfo.token.claim"=true'
10.3 Sử dụng Claims trong Application
// Ví dụ: Express.js middleware kiểm tra organization
import { Request, Response, NextFunction } from 'express';
interface OrganizationClaim {
[alias: string]: {
name: string;
roles: string[];
};
}
function requireOrganization(orgAlias: string) {
return (req: Request, res: Response, next: NextFunction) => {
const token = req.user; // decoded JWT
const orgs: OrganizationClaim = token.organization || {};
if (!orgs[orgAlias]) {
return res.status(403).json({
error: `User is not a member of organization: ${orgAlias}`
});
}
// Attach org info to request
req.organization = orgs[orgAlias];
next();
};
}
// Usage
app.get('/api/dashboard',
requireOrganization('acme-corporation'),
(req, res) => {
res.json({ message: `Welcome to ${req.organization.name}` });
}
);
11. B2B và B2B2C Use Cases
11.1 B2B Partner Portal
Scenario: Platform cung cấp portal cho partners
Realm: platform-realm
├── Organization: "Partner A" (partner-a.com)
│ ├── IdP: Partner A's Okta
│ ├── Members: 50 employees
│ └── Attributes: { plan: "gold", api_quota: "10000" }
├── Organization: "Partner B" (partner-b.com)
│ ├── IdP: Partner B's Azure AD
│ ├── Members: 200 employees
│ └── Attributes: { plan: "platinum", api_quota: "unlimited" }
└── Organization: "Partner C" (partner-c.com)
├── IdP: Partner C's Google Workspace
├── Members: 30 employees
└── Attributes: { plan: "silver", api_quota: "5000" }
Flow:
1. Partner employee truy cập portal
2. Nhập email → Keycloak detect organization từ domain
3. Redirect đến IdP của partner
4. Authenticate → token chứa organization claim
5. Application phân quyền dựa trên organization + plan
11.2 B2B2C SaaS Platform
Scenario: SaaS platform bán cho businesses, businesses mời end-users
Realm: saas-realm
├── Organization: "School A"
│ ├── Domain: school-a.edu.vn
│ ├── Members (Managed): Teachers, Admin staff
│ ├── Members (Unmanaged): Students (self-registered)
│ └── IdP: School A's LDAP → federated via Keycloak IdP
├── Organization: "School B"
│ ├── Domain: school-b.edu.vn
│ ├── Members (Managed): Teachers
│ └── Members (Unmanaged): Students
└── Individual users (no organization)
└── Free tier users
12. Best Practices
- Dùng Organizations thay vì multi-realm — giảm overhead quản lý, chia sẻ configs
- Verify domains — đảm bảo organization thực sự sở hữu domain trước khi auto-assign users
- Phân biệt Managed/Unmanaged — managed cho employees cần lifecycle management, unmanaged cho external users
- Sử dụng Identity-First Login — UX tốt hơn khi có nhiều organizations với different IdPs
- Bảo mật Invitation links — set expiration time hợp lý, monitor pending invitations
- Organization attributes cho authorization — dùng attributes để phân quyền theo plan, tier, region
- Monitor member count — đặt alert khi organization vượt quá quota