1. Organizations — Overview
Keycloak Organizations is a feature that allows to manage multi-tenancy in a single realm. Instead of creating multiple realms for each organization (tenant), you can create Organizations inside a realm to group users, manage domains, and control access by organization.
This is an important feature for the B2B (Business-to-Business) and B2B2C (Business-to-Business-to-Consumer) platforms, also known as CIAM (Customer Identity and Access Management).
1.1 Use Cases
| Scenario | Description |
|---|---|
| SaaS Multi-tenant | Each customer company is an organization, users belong to their organization |
| B2B Portal | Partner/vendor has its own organization, partner employees access the portal through organization |
| Enterprise with subsidiaries | The corporation has many subsidiaries, each subsidiary is an organization |
| Educational platform | Each school/institute is an organization, teachers/students are members |
2. Enable Organizations Feature
Organizations is a feature available in Keycloak 25+. To turn on:
2.1 Enable on Realm
# Qua Admin Console:
# Realm Settings → General → Organizations → Enabled
# Qua kcadm.sh:
kcadm.sh update realms/my-realm \
-s organizationsEnabled=true
2.2 Check status
# Qua REST API
curl -s "http://localhost:8080/admin/realms/my-realm" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" | jq '.organizationsEnabled'
# Output: true
3. Create and manage Organizations
3.1 Create Organization via Admin Console
Go to Admin Console → Organizations → Create organization:
| Field | Description | Example |
|---|---|---|
| Name | Organization name (required) | Acme Corporation |
| Alias | Unique, self-generated Alias from name | acme-corporation |
| Description | Organization description | Acme Corp - Enterprise customer |
| Redirect URL | URL redirect after member logs in | https://app.acme.com |
3.2 Create Organization via 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 allows to automatically associate users with organization based on email domain. When a user registers or logs in with an email belonging to the registered domain, Keycloak can automatically attach the user to the corresponding organization.
# 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 | Description |
|---|---|
| Verified | Verified domain — users with this email domain are automatically qualified for organization |
| Unverified | Unverified domain — only used for matching, requires admin approval |
Important note:
- A domain belongs to only a single organization
- Domain verification helps ensure that the organization actually owns that domain
- Subdomain matching:
acme.comwill match[email protected]but does not match[email protected]
5. Organization Attributes
Custom attributes allows saving additional metadata for 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 can be used in:
- Token claims — add organization metadata to access/ID tokens
- Authorization policies — authorization based on attributes
- Custom logic — handles business logic in application
6. Managing Members
6.1 Managed vs Unmanaged Members
| Type | Description | Example |
|---|---|---|
| Managed | User is fully managed by the organization — lifecycle is tied to the organization | Company employee: when leaving the organization, account is disabled |
| Unmanaged | Free user, only "joins" organization — account exists independently | Freelancer, contractor: can belong to many organizations |
6.2 Add existing users to 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 can be assigned roles in the context of organization:
| Role | Description |
|---|---|
| member | Default role — standard access |
| admin | Organization admin — can manage members and settings |
7. Invitation Management
Keycloak Organizations supports inviting users to join the organization via email invitations.
7.1 Send 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 | Description |
|---|---|
| Pending | Invitation sent, not accepted |
| Expired | Invitation expires (configurable expiration) |
7.3 Invitations Management
# 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 sends emails using templates that can be customized at Realm Settings → Email → Templates. The default template includes a link for users to accept invitations and create an account (if they don't have one).
8. Associate Identity Providers with Organizations
Each organization can have its own Identity Provider, allowing members to log in via their organization's IdP (for example, Acme Corp uses Google Workspace, Beta Inc uses Okta).
8.1 Associate IdP with 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 How it works
- User enters email on login page (identity-first login)
- Keycloak determines email domain → finds corresponding organization
- If the organization has a linked IdP → redirect user to that organization's IdP
- User authenticate with organization IdP
- Keycloak receives response and automatically attaches user to organization
9. Identity-First Login
Identity-first login is the flow where user enters email first, then Keycloak decides the appropriate authentication method based on organization membership.
9.1 Configuring Identity-First Login
When Organizations is enabled, Keycloak automatically creates flow "organization" for browser authentication. This flow works as follows:
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 Configure existing flow for Organizations
# Bind Organization flow cho Browser
kcadm.sh update realms/my-realm \
-s 'browserFlow=organization browser'
10. Mapping Organization Claims into Tokens
Keycloak can add organization information to access tokens and ID tokens, allowing the application to know which organization the user belongs to.
10.1 Organization Membership Mapper
Keycloak automatically adds organization claim when the Organizations feature is enabled. 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 Custom Mapper Configuration
You can add Organization Membership Protocol Mapper to the client scope to 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 Using Claims in 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 and 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
- Use Organizations instead of multi-realm — reduce management overhead, share configs
- Verify domains — ensures the organization actually owns the domain before auto-assigning users
- Managed/Unmanaged distinction — managed for employees who need lifecycle management, unmanaged for external users
- Use Identity-First Login — Better UX when there are multiple organizations with different IdPs
- Security Invitation links — set reasonable expiration time, monitor pending invitations
- Organization attributes for authorization — use attributes to assign authorization by plan, tier, region
- Monitor member count — set alert when organization exceeds quota