Apps currently talk OIDC directly to Entra. We insert Zitadel as the broker so it becomes the single SSO front-end, without breaking existing accounts.
Apps recognise returning users by the subject they stored. We make Zitadel emit
the same value by creating each Zitadel user with userId = Entra OID.
The app-facing sub is then identical to before → no app changes, no DB
rewrites, no fallback code. Avatars and extended data are carried over and
kept fresh by directory-sync.
Caveat — Microsoft's OIDC
subis pairwise (per app registration), not the OID. Apps keyed on theoidclaim or email preserve cleanly (e.g. Outline withOIDC_USERNAME_CLAIM=email). An app that stored Entra's pairwisesubcannot be kept stable by any broker → one-time email re-link. Run the discovery step to find these.
- Stand up the stack with
AZURE_*set; confirm the Entra IdP, project and apps are provisioned (tofu output). - Per-app discovery
For each app confirm what it keys on (oid / pairwise sub / email).
docker compose run --rm directory-sync discover-subject-keys
- Verify subject-preservation works
Confirms Zitadel accepts
docker compose run --rm directory-sync import-users --test-one
userId = <Entra OID UUID>and the fetched user id equals the OID. - Bulk import the affected users (before cutover):
Creates password-less users with
docker compose run --rm directory-sync import-users
userId = OID, plus metadata + avatar. - Switch each app's OIDC config from Entra-direct to Zitadel:
First federated login auto-links by email to the imported user →
OIDC_ISSUER=https://id.example.com OIDC_CLIENT_ID=<from tofu output> OIDC_CLIENT_SECRET=<from tofu output> OIDC_SCOPES=openid profile email urn:zitadel:iam:user:metadata
sub= OID. - Verify: log in; the app shows the existing account (no duplicate). Confirm avatar + extended attributes present.
The cutover does not stop apps or rewrite their databases.
If discovery proves an app stored Entra's pairwise sub, re-bind those users by
email in that app (one-time), or use that app's own identifier-migration path.
Per app: revert its OIDC env to Entra-direct and restart. Zitadel is middleware — no Entra-side change is needed; rollback is a config swap in minutes.