What this is about
In CIAS a person always becomes a user through the same flow. There are several variants, but no separate programs. The variants differ only in their policy: who may start the registration, which fields are required, where the tenant comes from, whether someone has to approve, and how long the link is valid. Each policy is in the configuration of the installation.
Why only one flow? Four copies of “check, create account, assign tenant, grant roles, notify, log” would drift apart over time. The copy that drifts is then the one that no longer enforces tenant isolation.
The variants side by side
| SELF_SERVICE | PLATFORM_ADMIN | TENANT_ADMIN | Redeem an invitation | |
|---|---|---|---|---|
| Who starts it? | the person themselves | platform administrator | tenant administrator | the invited person |
| Endpoint | POST /cias/registration/self | POST /cias/admin/registrations | POST /cias/tenant/registrations | POST /cias/registration/invitations/accept |
| Sign-in needed? | no, public | yes, with the roles from the policy | yes, as shipped: role tenant-admin | no, the link is the proof of identity |
| Tenant comes from | the policy, as shipped: new tenant; a hook may change it | the policy, for example the payload (allowed only here) | the caller's token, never the payload | the invitation |
| Typical case | a company signs up for the application | support creates an access by hand | an administrator invites a colleague | the colleague accepts the invitation |
TENANT_ADMIN and redeeming belong together: one sends the invitation, the other redeems its link. Which variants an installation offers is in its configuration. cias-runtime offers SELF_SERVICE and TENANT_ADMIN, the hub only SELF_SERVICE. An installation must first turn on PLATFORM_ADMIN, see Creation by the platform administrator.
The shared flow
All variants go through the same states. Only the entry and the approval differ.
stateDiagram-v2
direction LR
[*] --> PENDING_VERIFICATION: request accepted,<br/>email with link sent
PENDING_VERIFICATION --> VERIFIED: link clicked<br/>(or admin confirms)
VERIFIED --> PENDING_APPROVAL: approval needed
VERIFIED --> PROVISIONING: no approval needed
PENDING_APPROVAL --> APPROVED: approved
PENDING_APPROVAL --> REJECTED: rejected
APPROVED --> PROVISIONING
PROVISIONING --> COMPLETED: tenant, roles,<br/>welcome email
PROVISIONING --> FAILED: error
FAILED --> PROVISIONING: retry
PENDING_VERIFICATION --> EXPIRED: expired, replaced, discarded
PENDING_APPROVAL --> EXPIRED: expired, replaced, discarded
COMPLETED --> [*]
The states of a registration explains all states and transitions.
-
1CIASchecks whether the variant is turned on and whether the caller has the required roles; for
SELF_SERVICEit also checks the throttling -
2CIASdetermines the tenant and checks the fields against the variant's field descriptionA required field is missing or a hook rejects: 422
-
3CIAS→Keycloakfor a new address, creates the account disabled and unverified. For a known address, the account stays untouched
-
4CIAScreates a one-time link. Only its hash is stored
-
5CIAS→Emailsends its own email. Which one depends on whether the address is new
-
6User→CIASclicks the link
-
7CIASApproval needed? Then the registration waits in
PENDING_APPROVAL -
8CIAS→Keycloakenables a new account, assigns the tenant, grants the initial roles
-
9CIAS→Emailsends the welcome email; for a new account, if possible with a link to set the passwordResult: Registration
COMPLETED, the person can sign in
Each variant in the flow
When: Someone registers without signing in, through the public form.
-
1User→CIASfills in the form: email, name, company. No password
-
2CIASchecks the throttling (at most a few attempts per address)
-
3CIAS→Useranswers
202 { "status": "accepted" }, no matter whether the address is new or known -
4CIAS→EmailNew address: verification email. Known address: an email that uses the existing account
-
5User→CIASclicks the link
-
6CIASwaits for approval by a platform administrator, if the policy requires it
-
7CIAScreates a new tenant, key taken from the company name. The person gets the founder roles
Result: New tenant, the first person in it with the roles of the situation TENANT_FOUNDER. See Self-registration.
When: A platform administrator creates an access by hand.
-
1Admin→CIAS
POST /cias/admin/registrationswith email, fields and optionallytenantKey -
2CIASchecks the roles from
required-caller-roles, for exampleplatform-admin -
3CIASdetermines the tenant by the policy, with
FROM_PAYLOADfrom the payload. Only this variant may do that -
4CIAS→Emailemail with link to the person
-
5User→CIASclicks the link, then provisioning as always
Result: See Creation by the platform administrator.
When: A tenant administrator invites someone into their own tenant.
-
1Admin→CIAS
POST /cias/tenant/registrationswith email and name -
2CIASchecks the role (as shipped:
tenant-admin) -
3CIAStakes the tenant from their token. A
tenantKeyin the payload is ignored, not even checked -
4CIAS→EmailEmail with link. In
cias-runtimeit is valid for 14 days
Result: Invitation sent, the registration waits in PENDING_VERIFICATION. See Invitation by the tenant administrator.
When: The invited person accepts the invitation.
-
1User→CIASopens the link.
GET /invitations/{token}/formreturns the fields the inviting person left open -
2User→CIAS
POST /invitations/acceptwith the link token -
3CIASLink unknown or expired? 404. Already redeemed? The same answer as the first time
-
4CIASjoins the tenant from the invitation, grants the member roles
Result: The person is a member of the inviting person's tenant. See Redeem an invitation.
New or known address
In CIAS the email address is the account. There is one account per address and any number of tenant memberships. That is why every variant forks at the same point:
| Account for the address? | Is there a tenant to join? | Email and consequence |
|---|---|---|
| no | – | VERIFY_EMAIL – new account |
| yes | yes | MEMBERSHIP_INVITATION – joining another tenant. Password and account stay untouched |
| yes | no | ALREADY_REGISTERED – “You already have an account”, with a sign-in link |
The variant’s policy sets how long the link in the email is valid (token-ttl): in cias-runtime 24 hours for self-registration, 14 days for the invitation.
From the outside all three cases look the same: same status, same answer. Only the email differs. This way nobody can find out through the API whether an account exists for an address. More on this in The email is the account.
Where the tenant comes from
The tenant is determined in two stages:
-
CIASPolicy of the variante.g. SELF_SERVICE → new tenant, TENANT_ADMIN → from the token
-
HookHook of the projectmay override the policy, except when the tenant comes from the token (
FROM_CALLER) - The tenant is fixed. Provisioning checks whether it exists
Where the tenant comes from explains all six kinds of assignment (NONE, CREATE_NEW, JOIN_EXISTING, FROM_CALLER, FROM_PAYLOAD, FROM_INVITATION).
Which roles the person gets
A rule set per situation decides this:
| Situation | Applies to | Roles in the default setting |
|---|---|---|
TENANT_FOUNDER | first person of a new tenant | tenant-owner, tenant-admin, tenant-user (client roles) |
TENANT_MEMBER | joining an existing tenant | tenant-user (client role) |
TENANTLESS | registration without a tenant | user (realm role) |
The default setting is a bean in the application’s code. Platform and tenant administration can replace it with their own rules. How that works and who may do it is described in Initial roles as a rule set.
What the answer reveals (and what it does not)
- accepted: always
202withaccepted - second click:
202again - unknown or expired link:
404 - no state, no ID
- throttling:
429without a wait time
- the caller is known
- state and ID visible
- list of all registrations with a filter by state
403if the role is missing
Details in Protecting the public endpoints.
The endpoints at a glance
| Method and path | Variant | Purpose |
|---|---|---|
GET /cias/registration/flows/{flow}/form | all | field description for the form |
POST /cias/registration/self | SELF_SERVICE | register |
POST /cias/registration/verify | all | redeem the link |
GET /cias/registration/invitations/{token}/form | invitation | open fields of the invitation |
POST /cias/registration/invitations/accept | invitation | accept the invitation (redeem the link) |
POST /cias/admin/registrations | PLATFORM_ADMIN | create a person |
POST /cias/tenant/registrations | TENANT_ADMIN | invite a person |
GET /cias/admin/registrations | all | list, filter by state |
POST /cias/admin/registrations/{id}/approve · reject · retry · activate · discard | all | approval and maintenance, platform administrator only |
GET · PUT · DELETE /cias/admin/registration-rules/… | all | rules for the initial roles |