What this is about
Every tenant in CIAS has a type, in the code TenantType. It does not say what the tenant is in business terms. A customer is a customer, whatever its type. The type only says how Keycloak knows the tenant, and so how it gets into the token:
STATIC: Keycloak does not know the tenant at all. The person carries it as the user attributetenanton their account.DYNAMIC: Keycloak keeps the tenant as an organization. The person is a member of this organization.
The two types side by side
| STATIC | DYNAMIC | |
|---|---|---|
| In Keycloak | nothing | organization, its alias is the tenant key |
| How a person belongs to it | attribute tenant on the account | membership in the organization |
| Where it is in the token | claim tenant | claim organization |
| Tenants per person | one, plus switch targets in allowedTenants | any number of memberships |
| Own roles in the tenant | no, the roles of the token apply | yes, roles per organization |
| Field on the CIAS record | externalOrganizationId empty | externalOrganizationId = ID of the organization |
| Typical use | a few customers set up once; existing installations | self-registration of companies, people in several companies |
The alias of an organization is its short name in Keycloak, for example nordbau. CIAS always creates organizations so that the alias equals the tenant key. That is why the filter chain can read the tenant directly from the token.
What it looks like in the token
STATIC – Anna belongs to the tenant stadtwerke-nord for good
{
"sub": "3f2a…",
"tenant": "stadtwerke-nord",
"allowedTenants": ["stadtwerke-nord"],
"resource_access": { "cdms": { "roles": ["customer-read"] } }
}DYNAMIC – Ben is a member of the organization nordbau
{
"sub": "8c1d…",
"tenant": "nordbau",
"allowedTenants": ["nordbau"],
"organization": {
"nordbau": {
"id": "b7e0…",
"resource_access": { "cdms": { "roles": ["order-edit"] } }
}
}
}With a dynamic tenant, the key is in the token twice: as the organization and in the attribute tenant. This is on purpose. When CIAS assigns a person to a tenant, it always writes tenant and allowedTenants, also for an organization. Both values are the same, so they cannot contradict each other. If the attribute named a different tenant than the organizations, the filter chain would refuse the request, see Determine the tenant of a request.
Roles per type
When: Anna works in the static tenant stadtwerke-nord.
The client roles from resource_access and the realm roles apply. Every role Anna has applies in Anna's tenant. A second tenant with other roles is not possible.
Result: Simple and predictable: one person, one tenant, one set of roles.
When: Ben works in the dynamic tenant nordbau.
If the organization carries its own roles, these replace the global client roles. If it carries none, the global client roles apply. Realm roles always apply, and no organization extends them.
Result: Ben can be an editor in nordbau and only a reader in a second organization.
How the effective roles come about in detail is described in Effective roles: global or in the tenant.
How a tenant gets its type
The type is set when the tenant is created and does not change afterwards.
| Path | Type | What happens in Keycloak |
|---|---|---|
Self-registration of a company (CREATE_NEW) | always DYNAMIC | CIAS creates the organization, the alias is the new key |
Admin API POST /cias/admin/tenants | must be in the call, there is no default | nothing; for DYNAMIC the call names the ID of an organization that already exists |
| First tenant at startup (bootstrap) | from the configuration | nothing |
Why the admin API has no default: A dynamic tenant without an organization cannot be determined from any token. And a static tenant that does have an organization would be resolved for the wrong reason. So the call must say which type it means. More in Create and provision a tenant.
Which type fits?
| People belong to several companies? | Roles should differ per company? | Companies register themselves? | Recommendation |
|---|---|---|---|
| yes | – | – | DYNAMIC |
| – | yes | – | DYNAMIC |
| – | – | yes | DYNAMIC, self-registration only creates this type anyway |
| no | no | no | STATIC is enough; one person, one tenant, the roles from the token |
Both types may exist side by side in one installation. The type belongs to the tenant, not to the installation.