Chuyển đến nội dung chính

Lesson 14: Organizations - Multi-tenancy and CIAM

Enable and configure the Organizations feature, create/manage organizations, organization domains, organization attributes, manage members (managed, unmanaged), invitation management (send, track, resend, delete), associate Identity Providers with organizations, authenticating members (identity-first login), mapping organization claims to tokens and actual B2B/B2B2C use cases.

🔒 DevSecOps — Lesson 14 Lesson 14: Organizations - Multi-tenancy and CIAM

Keycloak from Basic to Advanced

Part 4: User Federation, Organizations and Authorization

xdev.asia

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

ScenarioDescription
SaaS Multi-tenantEach customer company is an organization, users belong to their organization
B2B PortalPartner/vendor has its own organization, partner employees access the portal through organization
Enterprise with subsidiariesThe corporation has many subsidiaries, each subsidiary is an organization
Educational platformEach 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:

FieldDescriptionExample
NameOrganization name (required)Acme Corporation
AliasUnique, self-generated Alias ​​from nameacme-corporation
DescriptionOrganization descriptionAcme Corp - Enterprise customer
Redirect URLURL redirect after member logs inhttps://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:

StateDescription
VerifiedVerified domain — users with this email domain are automatically qualified for organization
UnverifiedUnverified 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.com will 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

TypeDescriptionExample
ManagedUser is fully managed by the organization — lifecycle is tied to the organizationCompany employee: when leaving the organization, account is disabled
UnmanagedFree user, only "joins" organization — account exists independentlyFreelancer, 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:

RoleDescription
memberDefault role — standard access
adminOrganization 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

StateDescription
PendingInvitation sent, not accepted
ExpiredInvitation 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

  1. User enters email on login page (identity-first login)
  2. Keycloak determines email domain → finds corresponding organization
  3. If the organization has a linked IdP → redirect user to that organization's IdP
  4. User authenticate with organization IdP
  5. 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