The whole design, in writing
Learn system design by building the identity layer enterprise deals actually stall on. An interactive guide covering multi-tenant SAML and OIDC federation, a stable immutable user key, SCIM provisioning where deactivation is a PATCH rather than a DELETE, group-to-role mapping that survives a reorg, session and token revocation within the promised window, and the audit evidence a security reviewer asks for.
Every step of the build above, written out: the problem each piece solves, the option that was taken and the ones that were not, the numbers, and how it fails in production.
The big idea
Why identity blocks the deal
Nobody has ever lost an enterprise deployment because the model scored two points lower on an eval. They lose it because a security reviewer asked “what happens to access when someone leaves?” and the honest answer was “you email us”.
Build the identity layer properly: federate per tenant, key users on something immutable, provision over SCIM with deactivation handled correctly, map groups to roles through a layer their admin controls, revoke inside the promised window, and log everything a reviewer will ask for.
Step 1 · The skeleton
Let the right people in
The naive version: your own username and password, per user, in your own database. Every enterprise buyer refuses this within one meeting — it means their leavers keep working accounts, their password policy does not apply, and their multi-factor requirement is unenforceable.
Stand up an Auth Service that issues a session after the customer’s own identity system vouches for the user. You are a relying party, never a source of truth. Everything that follows makes that relationship multi-tenant, correct and revocable.
What the new pieces do
- Userclient
- Signs in with their work account and expects never to see a password prompt from you. They belong to the customer’s directory, not to yours — which is the fact that shapes the entire design.
- Auth Servicebackend
- Resolves which tenant a sign-in belongs to, then validates the assertion or token against that tenant’s configuration. One deployment, many trust relationships, no shared state between them.
Step 2 · Federate
SAML or OIDC — and the customer decides
One deployment, many customers, each with a different identity provider, a different protocol preference and a different set of attribute names. Hard-coding one of them means the next customer is a fork.
A large regulated customer insists on SAML 2.0. OIDC is technically better. What do you do?
All true, and irrelevant. Their federation service and their security team decided years ago. Arguing spends goodwill you need for the questions where you can actually change the outcome.
Protocol is tenant configuration, not an architectural commitment. Large regulated estates hand you SAML with no alternative, and being able to say yes immediately is worth more than the technical argument.
That closes the door on newer estates, mobile flows and machine-to-machine cases, and it commits you to XML signature validation as your only path — which is the easiest thing in this whole design to get subtly wrong.
Make protocol a per-tenant configuration: issuer, certificate, protocol, attribute mapping, all resolved at sign-in. Validate the assertion’s own signature, audience, recipient and time bounds — and pin the certificate with its expiry in a calendar.
- per tenantprotocol + config
- sign the assertionnot just the wrapper
- pin the certand calendar it
What the new pieces do
- Identity Providerclient
- The customer’s own identity system. It decides who exists, who they are, and — through SCIM — when they stop being an employee. You are a relying party, never the source of truth.
- SAML / OIDCservice
- Signature on the assertion (not just the response wrapper), audience, recipient, time bounds and in-response-to. Certificate pinned, and its expiry in a calendar — rotation is the classic 12-to-24-month outage.
Back of the envelope
- support both, lead with OIDC
- the customer decides
- validate assertion signature
- audience, recipient, time bounds
- reject unsigned assertions
- no fall-back branch
- certificate expiry tracked
- the classic 12–24 month outage
Step 3 · Key it correctly
The identifier that does not change
You keyed users on email, and it worked perfectly until the customer was acquired and every address changed on one weekend. Now every user is a stranger, their history is orphaned, and the fix is a data-repair project nobody budgeted for.
Store the User Record under the provider’s immutable subject identifier — oid in Entra, a persistent-format NameID in SAML — and treat email as a display attribute that is allowed to change.
- immutable subject idthe key
- emaildisplay only
- retrofit costa data migration
What the new pieces do
- User Recordservice
- Keyed on the provider’s immutable subject identifier, with email as a display attribute. This one decision is what survives a marriage, a rebrand and an acquisition without a data-repair project.
Back of the envelope
- keyed on immutable subject identifier
- oid / persistent NameID
- email is a mutable attribute
- not an identity
- attribute mapping explicit per tenant
- names differ everywhere
Step 4 · The question that decides
SCIM, and the PATCH nobody implements
Single sign-on answers who is this. It does not answer should this person still have an account. A user removed from the directory cannot sign in — and their account, their API tokens and their scheduled exports all still exist.
You implemented SCIM DELETE. The customer offboards someone. What happens?
Only if their identity provider calls DELETE — and most do not. Entra and Okta typically deactivate rather than delete, precisely so that history and audit trails survive the offboarding.
There is no reconciliation sweep in this design, so nothing is delayed. Nothing happens at all, and no error is raised.
The integration reports success, the dashboard stays green, and the departed employee keeps their access. It is the most common enterprise SSO defect there is, and it is exactly what gets tested.
Implement the SCIM Endpoint properly: idempotent creates, filtered lookups, pagination, and PATCH active=false as the primary deactivation path. If you cannot ship SCIM in time, offer a documented daily reconciliation as a compensating control with a date — reviewers accept that far more often than people expect.
- PATCH active=falsethe real path
- idempotentproviders retry
- paginatetenants have thousands
What the new pieces do
- SCIM Endpointservice
- Idempotent, paginated, and correct about the one operation that matters: deactivation arrives as PATCH active=false far more often than as DELETE.
Back of the envelope
- PATCH active=false handled first
- DELETE is the rare case
- idempotent creates
- IdPs replay operations
- 409 on duplicate userName
- correct status codes matter
- every operation logged
- this is audit evidence
Step 5 · Survive the reorg
Groups map to roles through a layer they control
You mapped their directory groups straight to permissions. It works for a month. Then a reorg renames FIN-AP-ANALYSTS, forty people lose access on a Monday, and you are paged for a change nobody told you about.
Put a Group → Role table in between, editable by their admin in a screen. Their groups map to your small fixed role set — three is usually enough at go-live — which maps to permissions. Keep data scoping on a separate axis.
- 3 rolesat go-live
- their admin editsnot you
- unmapped→ deny, loudly
What the new pieces do
- Group → Roledata
- Their directory groups map to your small fixed role set through a table their own admin edits. Data scoping — "approver for EMEA only" — stays on a separate axis, or the role list explodes.
Back of the envelope
- customer-editable mapping table
- every change logged and attributed
- small fixed role set
- roles are your product, groups are theirs
- data scoping on a separate axis
- or the role list explodes
- unmapped user denied with a message
- never a silent default
Step 6 · Make revocation true
Sessions and tokens die with the account
Deactivation blocks new sign-ins. The user’s current session is still valid, their API token still works, and their nightly export still runs. You committed to revoking access within four hours and you have revoked nothing.
Propagate deactivation to Sessions and Tokens: short lifetimes plus a revocation check, so access ends inside the promised window rather than whenever the user next signs in — which may be never.
- sessionsrevoked
- API tokensrevoked
- windowyou promised it
What the new pieces do
- Sessions + Tokenscache
- Short-lived tokens with a revocation check, so deactivation ends access inside the window you promised rather than at the user’s next sign-in — which may never come.
Back of the envelope
- deactivation revokes sessions and tokens
- not just new logins
- short token lifetimes
- bound the worst case
- scheduled jobs re-check identity
- exports outlive employment
Step 7 · Prove it
The evidence a reviewer asks for
The security review asks for a joiner–mover–leaver walkthrough, an audit log sample, and a break-glass procedure. You have all three behaviours and none of them written down, so the review takes three more weeks.
Log to an immutable Audit Log: every authentication, permission change, provisioning event and break-glass use — retained to their policy and exportable to their SIEM. Then package the artefacts: an identity architecture diagram, the JML walkthrough, ten real redacted log lines, the break-glass procedure, and a named gap list with dates.
- 10 real linesbeat any assurance
- break-glassthey always ask
- gap listvolunteered
What the new pieces do
- Audit Logdata
- Every authentication, permission change, provisioning event and break-glass use — immutable, retained to their policy, exportable to their SIEM. Ten real lines end a review that assurances do not.
Back of the envelope
- auth, permission, provisioning, break-glass
- all logged immutably
- exportable to their SIEM
- their tooling, not yours
- JML walkthrough written
- the leaver paragraph is the one they read
- named gaps with dates
- volunteered, not discovered
You did it
You just designed enterprise SSO & provisioning.
Everything you assembled, in order
- You are a relying party — the customer’s directory decides who exists and who has left.
- Protocol is per-tenant configuration: support both, lead with OIDC, expect SAML.
- Key users on the immutable subject identifier; email is a display attribute.
- Deactivation arrives as PATCH active=false — a DELETE-only SCIM implementation fails silently.
- Map their groups to your small fixed roles through a table their own admin edits.
- Revocation must reach live sessions and API tokens inside the window you promised.
- Package the evidence: diagram, JML walkthrough, real log lines, break-glass, and a volunteered gap list.