Skip to main content

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.

Before you switch

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 opaque bot_… token is refused in identity mode.
  • 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.

#ConditionHow to check
1identity is deployed and answers on its public nameCoordinator 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.
2identity 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.
3identity's keys are pinned in the desired statepinned_keys ≥ 1 and a recent pinned_at (the coordinator fetches the JWKS every 5 minutes). Blocker: identity's keys are not pinned yet.
4The coordinator's public URL is setSettings → public address. Without it the services cannot reach the RBAC projection. Blocker: no public URL: ….
5A break-glass account is enrolled and testedSettings → "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.
6Clients that sign in through identity are installed — iOS, macOS, Android, webThe 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.
7Worker bots hold identity credentialsidentity 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.
8The directory jobs are handed over to identityCoordinator 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.
9A 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.
10Sign-in domains and the company provider are set in identityidentity 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.
11A pair of identity nodes, if the installation needs oneAfter 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.
12Service 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 /healthz has a platform_token field reading mode: 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​

  1. 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.

  2. Go through section 2; blockers is empty.

  3. 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.

  4. 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"}' | jq

    The answer is the same view as GET, with mode: identity and door: 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:

    BlockerWhat 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 error in the answer
    no public URL: the services cannot reach the RBAC projectionitem 4
    no break-glass credential is enrolleditem 5

    The coordinator's audit shows platform_token.set with identity.

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 carddav instead 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 code sign_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 in identity mode 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_issuer and platform_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_identity means "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) — the platform_token field:
    • mode: identity, issuer exactly identity's public address;
    • revocations.cursor grows or holds, revocations.last_ok no older than a minute, revocations.error empty, revocations.stale: false (stale_after is the bound, 10m0s by default; with stale: true the service refuses tokens older than the bound);
    • rbac.loaded: true, rbac.last_ok no older than 30 seconds, rbac.error empty;
    • on the coordinator, door: unavailable instead of those fields — it cannot verify identity's token; read error in GET /admin/api/settings/platform-token.
  • identity's /healthz — platform_issuer: {mode: on, feed_head}; feed_head is the head of the revocation feed, and the services' cursors should keep up with it.
  • identity's audit — the platform.token stream (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; any breakglass.signin is 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: platform has lost its opaque token — in hmac it cannot sign in until a new token is issued in the coordinator's console;
  • a guest invited through identity in identity mode 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/renew doors 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/breakglass stays.
  • 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's ldapdir is the only LDAP reader left.
  • Messaging's opaque bot_… tokens (once every bot takes its token from identity; a re-create with credential: platform removes 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, or PUT /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​

WhatWasNow
People (accounts, disabling, deleting, passwords)coordinator → Usersidentity → People. The coordinator's Users keeps a person's roles and devices
Groups and membershipcoordinator → Users → Groupsidentity → Groups. The coordinator keeps only "Groups → roles"
Roles and permissionscoordinator → Users → Rolesunchanged, the coordinator
Sign-in through a company providercoordinator → Users → the "Single sign-on (OIDC)" cardidentity → 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 codescoordinator → Users (filter guest), "Access recovery", guest-max-daysidentity → "Guests" and the guest policy (max_days, default_days, max_per_sponsor); in the app as before
Bots: creatingcoordinator → AI & automation → Botsstill there (the record is written to identity and messaging)
Bots: owner, keys, secrets, hand-over, reviewnoneidentity → "Bots", the review policy's bot_days
The LDAP connection, groups and people from LDAPcoordinator → Settings → LDAPidentity → "Directory"
Photos from AD/LDAPcoordinator → Settings → "Directory photos (AD / LDAP)"identity → "Directory"; the photo policy unchanged
SimpleOne (HRMS)coordinator → Settings → "SimpleOne (HRMS)"identity → "Directory"
LDAP password pass-throughcoordinator → LDAPidentity, PUT /control/ldap/pass-through (since 28.09)
Access to the servicesnoneidentity → Policies / People / Groups → "Access to SimpleTwo"
The token switchnonecoordinator → 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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 identity mode 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.
  5. 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.
  6. 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.
  7. 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 for service_token: keys across the fleet.
  8. Provisioning. Of the services only the calendar reads the provisioning feed; messaging, mail, drive and telephony do not. Whether to switch provisioning on with the flip or later is not decided.
  9. The length of the stable period and the criterion that starts section 5's removals are not set; after them there is no rollback.
  10. Rolling back bots and guests created in identity mode is not described (section 4).
  11. Calls in progress at the switch: a messaging restart may drop them; whether to wait for a window without calls is not decided.
  12. The Windows client signing in through identity is not built; where an installation has it, its users cannot sign in after the switch.