Switching sign-in to identity
Applies to: identity after 0.36, a coordinator with the platform_token setting · Checked: 01.10.2026
This is the runbook for an installation's operator. The decision is ADR-0047 §8a: identity
issues the tokens every SimpleTwo service accepts. Today the coordinator mints a person's
session (an HMAC token under the shared secret ST_TOKEN_SECRET); after the switch the
coordinator leaves the sign-in path and keeps the fleet and the roles (RBAC).
There is one switch — the coordinator's setting platform_token, hmac (the default) or
identity. While it is hmac, nothing below applies.
The switch checks five conditions itself and will not turn on until they hold. The others — app releases that sign in through identity, the platform bots' credentials, the directory jobs handed over, the provisioning decision — are in the checklist below: go through all of it.
1. What changes
For people. Sign-in happens only on identity's page, in the system browser: a Safari sheet
(ASWebAuthenticationSession) on iOS and macOS, Custom Tabs on Android, an ordinary tab for the
web client. The app never sees the password again. Password, second factor, passkey, push
approval, e-mail codes, password reset — all on identity's one page. Where the organisation
signs in through its own ADFS, Entra or Keycloak, identity's page redirects there. At the switch
everybody signs in again, once: the app says "sign in again" and opens identity's page.
After that, staying signed in works as today: the token lives 12 hours and is refreshed at
identity, and when identity is unreachable a signed-in person keeps working for as long as the
service still hears the revocation feed: a service that has not read it for longer than the bound
(10 minutes by default, Settings → "Sign-in through identity") refuses tokens issued before the
bound — such a token may have been revoked without the service knowing. A token issued since
passes.
Sign-out, "This wasn't me", a password reset, "sign out everywhere" and a disabled account now take effect in every service at once: a person has one session, and its end reaches chat, calendar, mail and the console within 30 seconds.
For administrators.
- The coordinator's console (
/admin) is signed in to through identity. The console's local login, the SSO card and the coordinator's local user passwords go. The one local sign-in left is the break-glass account at/admin/breakglass. - People, groups, membership, the sign-in policy, sign-in domains, company providers, guests,
bots, LDAP and HRMS are managed in identity's console (
identity_url/admin). The fleet, the roles and the "group → roles" binding stay in the coordinator's console. - Roles are still granted in the coordinator's console and take effect at once: the token carries no roles, every service asks the coordinator what a person may do and refreshes the answer every 15 seconds.
For guests. A guest is an identity account with a sponsor (the inviting employee). Inviting, extending, revoking and the recovery code work in the app as before, but identity performs them. A guest signs in on identity's page like everyone. A meeting's or a chat's guest link is not an account and does not change.
For bots and integrations.
- A worker bot (the support bot, announcements, integrations) gets its token from identity
by
client_credentials,client_id=bot:[bot name], with a key (private_key_jwt) or a secret. Messaging's opaquebot_…token is refused inidentitymode. - An assistant, whose brain runs on its owner's device, gets its token by exchanging the owner's (RFC 8693); nothing to do — the owner's updated app does it.
- Every bot has an owner. An owner disabled or deleted takes their bots and assistants with them (except the organisation's system bots, which the console flags instead).
- Protocol logins (SMTP, JMAP, CalDAV, CardDAV, WebDAV, LDAP) use identity's app passwords. The calendar's CalDAV passwords and CardDAV's session-token-as-password go.
- Third-party applications already signing in through identity over OIDC, SAML or LDAP notice nothing.
2. Checks before the switch
The switch itself refuses (409 with the list) until the first five items hold, and the part of item 7 about the platform's own bots. It does not check the rest — the operator does.
Below, $S2 is the coordinator's public address (https://s2.example.com), $ID identity's
public address (https://id.example.com), $ADMIN_TOKEN a coordinator console token with
settings:read/settings:write, $ID_TOKEN an identity console token of an identity
administrator.
The coordinator's console → Settings → the "Sign-in through identity" card shows both switches
(platform_token and service_token) and everything standing in each one's way, linked to where
it is fixed; and, third, the CalDAV and CardDAV switch (dav_auth). The same in one request — it shows every check the switch makes:
curl -fsS "$S2/admin/api/settings/platform-token" -H "Authorization: Bearer $ADMIN_TOKEN" | jq
The answer: mode, issuer, identity_issuer_on, pinned_keys, pinned_at, door,
blockers (an empty list — you may switch), error, remaining.
| # | Condition | How to check |
|---|---|---|
| 1 | identity is deployed and answers on its public name | Coordinator console → Settings → the Identity card: the name, the reach (direct/nat), what it resolves to, "announced". curl -fsS "$ID/healthz" with ordinary TLS verification. The discovery document curl -fsS "$S2/.well-known/simpletwo.json" carries identity_url. Blocker: identity is not deployed with a public name. |
| 2 | identity issues the platform token (platform_issuer: on) | curl -fsS "$ID/control/platform-issuer" -H "Authorization: Bearer $ID_TOKEN" → {"mode":"on"}. To switch it on: the same address, PUT with {"mode":"on"} (audited as platform_issuer.set). $ID/healthz shows platform_issuer.mode: on. The same in identity's console → Policies → "Platform token issuer". Blocker: identity's platform issuer is off. |
| 3 | identity's keys are pinned in the desired state | pinned_keys ≥ 1 and a recent pinned_at (the coordinator fetches the JWKS every 5 minutes). Blocker: identity's keys are not pinned yet. |
| 4 | The coordinator's public URL is set | Settings → public address. Without it the services cannot reach the RBAC projection. Blocker: no public URL: …. |
| 5 | A break-glass account is enrolled and tested | Settings → "Break-glass account": state "active". An owner enrols it (password of 12 or more + TOTP; a replacement is pending until its first code). Test the sign-in: open $S2/admin/breakglass, sign in with the password and a code, see the console open, sign out. The audit log shows breakglass.signin, and owners and administrators get a message from the support bot. Keep the password and the authenticator where the on-call administrator can reach them without the platform. Blocker: no break-glass credential is enrolled. |
| 6 | Clients that sign in through identity are installed — iOS, macOS, Android, web | The app release with sign-in through the system browser (item 7) and the coordinator release with the web client's and the console's sign-in page. Adoption: coordinator console, the device list — platform and app version of each device (see Clients, "Fleet monitoring"). Whoever has not updated cannot sign in after the switch until they update. |
| 7 | Worker bots hold identity credentials | identity console → "Bots": every bot has an active owner and a key or a secret; hand ownerless ones over. The bot already asks identity for its token: POST $ID/oauth/token with grant_type=client_credentials answers with a token (no service accepts it before the switch — that is expected). The platform's own bots move by themselves, and the switch checks it: only the support bot of each support case signs in (the welcome bot posts through messaging's automations, the calendar's cards are written by the assistant on the calendar's service token, the MCU uses its own bridge key — none of them needs a credential). Once identity is deployed, the agent on the support host makes the bot's key itself (/var/lib/simpletwo/bot-key; the private half never leaves the host), the coordinator registers the public half at identity, and from the switch on the bot gets its token from identity by client_credentials. Such a bot's owner is the administrator who ran the support provisioning; the bot is a system bot and is not disabled when its owner leaves — identity's console offers a hand-over. Check: identity console → "Bots" shows a key for the support bot; the support host's desired state carries bot_key_kid. If not, the support node's reports show platform bot key: …. Blocker: platform bots without identity credentials: …, listing the bots and their support cases. |
| 8 | The directory jobs are handed over to identity | Coordinator console → Settings: the LDAP, "Directory photos (AD / LDAP)" and "SimpleOne (HRMS)" cards say "managed on identity" with a link. identity console → "Directory" shows the connection, the watermarks and the last results of the group, people and photo passes. If a card says why there was no hand-over (no identity, or one too old for the hand-over door), update identity and messaging first, then the coordinator. |
| 9 | A decision on provisioning (provisioning) | identity console → Policies → "Access to SimpleTwo". For a running installation one of two is safer at the switch: leave it off (everyone reaches every service, as today) or switch it on with "Everyone gets messaging and calendar". Switching on "by assignment only" without the option withdraws chat and calendar from everyone with nothing assigned. Details: Access to SimpleTwo. |
| 10 | Sign-in domains and the company provider are set in identity | identity console → sign-in domains: every domain people sign in with has the right owner — identity or a provider. The provider (ADFS, Entra, Keycloak, SAML) is configured in identity and tested by a test account signing in at $ID. The coordinator's SSO card stops working at the switch. See Sign-in domains, ADFS, SAML. |
| 11 | A pair of identity nodes, if the installation needs one | After the switch identity is on the critical path: without it nobody signs in anew (the signed-in keep working for up to 12 hours). An installation that cannot accept that needs a second node and a replicated database before the switch. Not built as of 30.09.2026: see the questions below. |
| 12 | Service tokens under keys (service_token: keys, item 4) | Not landed as of 30.09.2026. Until it lands the services' tokens are signed with the shared secret, and ST_TOKEN_SECRET cannot be removed after the switch. |
Also, before the window:
- every service (messaging, calendar, mail, identity) runs a release with the new door: each
one's
/healthzhas aplatform_tokenfield readingmode: hmac; - accounts with no home on identity: coordinator console → Identity → the drift ("accounts here with no home on identity"), "write missing accounts" where needed. An account with no record on identity cannot sign in after the switch.
3. The switch
Order
-
Announce the window: to people — "at [time] the app will ask you to sign in again"; to the on-call — a channel outside the platform.
-
Go through section 2;
blockersis empty. -
Keep a second console session open — the break-glass sign-in in a separate window (
$S2/admin/breakglass), in case sign-in through identity does not work. -
Switch: in the console — the "Sign-in through identity" card → "Switch to identity…", then type the word
identity; or by request:curl -fsS -X PUT "$S2/admin/api/settings/platform-token" \
-H "Authorization: Bearer $ADMIN_TOKEN" -H 'Content-Type: application/json' \
-d '{"mode":"identity"}' | jqThe answer is the same view as
GET, withmode: identityanddoor: identity. Before saving, the coordinator fetches the keys again and asks identity whether it issues — it does not trust the last checks. If something is wrong — 409 with{"error": "the platform token cannot be switched to identity yet", "blockers": [...]}, and nothing changes:Blocker What to do identity is not deployed with a public nameitem 1 — DNS, the certificate, "announced" on the Identity card identity's platform issuer is off (Identity → platform-issuer)item 2 — PUT /control/platform-issuer {"mode":"on"}identity's keys are not pinned yetitem 3 — wait up to 5 minutes, or read errorin the answerno public URL: the services cannot reach the RBAC projectionitem 4 no break-glass credential is enrolleditem 5 The coordinator's audit shows
platform_token.setwithidentity.
What happens at that moment
- The coordinator switches at once, by its own setting. Its doors (the apps, the console)
accept only identity's token; the directory over CardDAV takes identity's app password with the
scope
carddavinstead of the session token. Password sign-in (/v1/auth/login), refresh (/v1/auth/refresh), the SSO callback and the console's local login answer "people sign in through identity". A person's HMAC session is refused with the codesign_in_through_identity. - messaging, calendar, mail, identity receive a new desired state (
ST_PLATFORM_TOKEN=identity, the issuer, the pinned keys, the feed's and the RBAC's addresses), re-render their environment and restart. The agent polls the coordinator once a minute — for 1–2 minutes hosts still run the old mode until they restart. A service inidentitymode that lacks the issuer, keys, the feed or the RBAC does not start — visible in "Servers & deploys". - The discovery document announces
platform_token: identity,platform_issuerandplatform_client_id: "simpletwo". The apps switch to browser sign-in from it. - Every service starts reading identity's revocation feed every 30 seconds and the coordinator's RBAC projection every 15 seconds. Until the RBAC has been read once, a service refuses everything that needs a permission.
What keeps the old path until item 4 and item 8: the services' own tokens, rooms, a bot's session
at the coordinator, a meeting guest's chat credential from a link. A guest whose token was minted
before it carried knd signs in again.
What people see
- One "sign in again" in each app at its first request after the switch. The app does not retry
in a loop: the code
sign_in_through_identitymeans "sign in once". Sign-in is on identity's page; for an organisation with a company provider — a redirect to it. - Whoever has not updated the app sees a sign-in error: an old app cannot sign in through the browser. The answer is to update.
- A call started before the switch may drop on reconnect when its service restarts.
The first hour
Look every few minutes:
- Each service's
/healthz(the coordinator, messaging, calendar, mail, identity) — theplatform_tokenfield:mode: identity,issuerexactly identity's public address;revocations.cursorgrows or holds,revocations.last_okno older than a minute,revocations.errorempty,revocations.stale: false(stale_afteris the bound,10m0sby default; withstale: truethe service refuses tokens older than the bound);rbac.loaded: true,rbac.last_okno older than 30 seconds,rbac.errorempty;- on the coordinator,
door: unavailableinstead of those fields — it cannot verify identity's token; readerrorinGET /admin/api/settings/platform-token.
- identity's
/healthz—platform_issuer: {mode: on, feed_head};feed_headis the head of the revocation feed, and the services' cursors should keep up with it. - identity's audit — the
platform.tokenstream (a platform token issued, one per sign-in and refresh) grows as people sign in; sign-ins and refusals are in the sign-in log (Recent sign-ins). - The coordinator's audit —
platform_token.set; anybreakglass.signinis a sign that somebody could not sign in through identity. - How many signed in: the coordinator's device list and identity's sign-in log; by the end of the hour most active devices should have signed in.
- Errors at the services: a burst of 401 at messaging and the calendar in the first minutes is the expected "sign in again"; 403 on actions that need a permission means the RBAC is not being read.
Check by scenario, not by one /healthz: sign-in on iOS, Android, macOS and the web client; a
message to a second device; a calendar event; a letter through JMAP; signing in to the
coordinator's console through identity; "sign out everywhere" for a test account — within 30
seconds every device of it is refused.
4. Rollback
Going back is always accepted and checks nothing:
curl -fsS -X PUT "$S2/admin/api/settings/platform-token" \
-H "Authorization: Bearer $ADMIN_TOKEN" -H 'Content-Type: application/json' \
-d '{"mode":"hmac"}'
If the console does not open through identity, sign in with the break-glass account
($S2/admin/breakglass): its session opens the console in both modes.
What survives. Everything identity holds: accounts, groups, identity's sessions, the revocation journal (written in both modes), assignments, identity's guests and bots, the handed-over directory jobs. The coordinator's sessions and refresh tokens were not deleted — they go only after the stable period (section 5).
What people see. "Sign in again" once more: discovery says hmac again, the apps go back to
the old sign-in, and services in hmac mode do not accept identity's token. A session the app
dropped at the switch does not come back — people sign in anew.
Where the rollback is not whole (check before the switch, see the questions below):
- a bot re-created with
credential: platformhas lost its opaque token — inhmacit cannot sign in until a new token is issued in the coordinator's console; - a guest invited through identity in
identitymode has a projection on the coordinator but may have no password for the old sign-in; - after the removals of section 5 there is no rollback.
5. After a stable period
The removals of ADR-0047 §8a, each a release of its own. Order: first what nothing depends on, the shared secret last.
- A person's HMAC session token: minting and checking at the coordinator, messaging,
the calendar, mail, identity; the
/v1/auth/renewdoors for people. - The coordinator's refresh tokens (
/v1/auth/refresh, their table). - The sign-in paths through the coordinator:
/v1/auth/login,/v1/auth/passkey/*, the SSO callback, the console's local login/admin/api/login, the SSO card, local user passwords./admin/breakglassstays. - The coordinator's copy of the directory jobs (the owner's decision of 30.09.2026: it goes
with the switch):
ldapphoto,ldapgroups.go,ldappeople.go,avatarsync.go,hrms.go,integration.go, their cards, settings and the hand-over's fallback. identity'sldapdiris the only LDAP reader left. - Messaging's opaque
bot_…tokens (once every bot takes its token from identity; a re-create withcredential: platformremoves the dead hash). - The calendar's CalDAV passwords and CardDAV's session-token-as-password — once protocol
logins use identity's app passwords. Since 01.10.2026 the coordinator's CardDAV in this
mode already takes an app password (
carddav); the calendar's device passwords go with a separate switch,dav_auth(the "Sign-in through identity" card → CalDAV and CardDAV, orPUT /admin/api/settings/dav-auth), once people have made app passwords. -
ST_TOKEN_SECRET— only after service tokens under keys (item 4): until then the services' tokens are signed with it. - The revocation journal stays; the coordinator's fifteen-second pull and the revocation push for HMAC sessions go.
6. Where things were, and where they are now
| What | Was | Now |
|---|---|---|
| People (accounts, disabling, deleting, passwords) | coordinator → Users | identity → People. The coordinator's Users keeps a person's roles and devices |
| Groups and membership | coordinator → Users → Groups | identity → Groups. The coordinator keeps only "Groups → roles" |
| Roles and permissions | coordinator → Users → Roles | unchanged, the coordinator |
| Sign-in through a company provider | coordinator → Users → the "Single sign-on (OIDC)" card | identity → company providers and sign-in domains |
| The console's local login | /admin, username and password | /admin → identity's page; in an emergency /admin/breakglass |
| Guests: inviting, expiry, revoking, recovery codes | coordinator → Users (filter guest), "Access recovery", guest-max-days | identity → "Guests" and the guest policy (max_days, default_days, max_per_sponsor); in the app as before |
| Bots: creating | coordinator → AI & automation → Bots | still there (the record is written to identity and messaging) |
| Bots: owner, keys, secrets, hand-over, review | none | identity → "Bots", the review policy's bot_days |
| The LDAP connection, groups and people from LDAP | coordinator → Settings → LDAP | identity → "Directory" |
| Photos from AD/LDAP | coordinator → Settings → "Directory photos (AD / LDAP)" | identity → "Directory"; the photo policy unchanged |
| SimpleOne (HRMS) | coordinator → Settings → "SimpleOne (HRMS)" | identity → "Directory" |
| LDAP password pass-through | coordinator → LDAP | identity, PUT /control/ldap/pass-through (since 28.09) |
| Access to the services | none | identity → Policies / People / Groups → "Access to SimpleTwo" |
| The token switch | none | coordinator → Settings → "Sign-in through identity" (GET/PUT /admin/api/settings/platform-token, /service-token); identity's issuer — identity's console → Policies → "Platform token issuer" |
7. Directory mode — the product option ID
Since identity 0.46 the role has two modes. Provider (the default) is everything above:
its own credentials and factors, the OIDC/SAML provider for the company's applications, the
LDAP server, the platform issuer. Directory — identity keeps people, groups, aliases,
contacts, mailboxes and provisioning, takes SCIM, brokers sign-in to the company's provider;
people's sign-in is the coordinator's, as before the switch. In directory mode the provider's
doors answer 409 provider_off, the sign-in page offers the company provider's buttons alone,
the LDAP listeners are not opened, and the policy switches that would turn a factor or the
provider on are refused with the same code. The coordinator's doors (storing and verifying
passwords, guests' codes, app passwords) are open in both modes: the coordinator is the core's
authenticator and keeps in the directory what it verifies sign-in with.
The coordinator's console sets the mode: Settings → Product options → ID. An option turned
off reaches the identity host with its desired state (the mode parameter, the DIR_MODE
variable) on its next poll. The option cannot go off while platform_token is identity:
back to hmac first. The identity console shows directory mode as a card on the Policies tab;
/healthz reports mode.
Open questions
What ADR-0047 leaves undecided and an operator will hit:
- Who announces the window, and how. Nobody is named to tell people about signing in again, nor the channel — SimpleTwo itself (a bot, a banner in the app) or the organisation.
- How long to wait for clients to update. There is no threshold of updated devices and no interval between the client release and the switch; nor a way to make an old app update other than the "sign in again" message.
- No button — resolved 01.10.2026: the "Sign-in through identity" card in the coordinator's Settings (both switches, the blockers with links, a two-step switch with a typed word, the way back) and "Platform token issuer" in the policies of identity's console.
- Guests at the switch — reconciled 30.09.2026. Item 7 was built keeping a guest's HMAC
session, item 8 made guests identity's accounts; the integration follows item 8, as this
runbook does: in
identitymode a guest account signs in on identity's page (the apps pass the guest's name as the hint), and only a meeting guest's chat credential keeps its HMAC path. - The platform's bots — done 01.10.2026 (ADR-0047 §8a item 8, "The platform's own bots"): the support bot signs in with its host's key, and the switch checks that it has one (item 7 of the checks). There is no dedicated "system owner": the owner is the administrator who ran the provisioning.
- identity's availability. The ADR asks for two nodes and a replicated database; whether it is required before the switch, and for which installations, is not decided. The failover of the keys' and feed's addresses across nodes is built separately and not released.
ST_TOKEN_SECRET. §8a's Order removes it with the switch, but service tokens are signed with it until item 4 is switched on — item 4 has landed switched off (service_token: hmac), and the secret's removal waits forservice_token: keysacross the fleet.- Provisioning. Of the services only the calendar reads the provisioning feed; messaging,
mail, drive and telephony do not. Whether to switch
provisioningon with the flip or later is not decided. - The length of the stable period and the criterion that starts section 5's removals are not set; after them there is no rollback.
- Rolling back bots and guests created in
identitymode is not described (section 4). - Calls in progress at the switch: a messaging restart may drop them; whether to wait for a window without calls is not decided.
- The Windows client signing in through identity is not built; where an installation has it, its users cannot sign in after the switch.