Appearance
8.4.6 — SSO, Federation and Provisioning
An employee joins a company on Monday. By Tuesday morning they can use fourteen applications, none of which anyone told about them individually. On Friday they leave, and by Friday evening all fourteen have stopped working.
Nothing in Chapters 8.4.3 to 8.4.5 does that. Those protocols authenticate a user who already exists. Getting the account created, kept in step and removed is a separate mechanism, and the gap between the two is where most real access-control failures live: the employee who left in March and still had an active account in September.
1. What single sign-on actually is
Single sign-on means one authentication event grants access to many applications. The mechanism is three separate sessions, and almost every confusing SSO behaviour comes from their lifetimes differing.
The identity provider session — a cookie on the identity provider's domain, saying this browser has authenticated. Typically hours to days.
The application session — your own cookie, created after a successful assertion or ID token. Typically shorter.
The browser — which carries both and can be closed at any point.
First visit: the application has no session, redirects to the identity provider, which has no session either, so it prompts for credentials and a second factor. Assertion returns, application session created.
Second application, ten minutes later: it redirects to the identity provider, which does have a session, so it issues an assertion immediately with no prompt. The user experiences this as "it just worked", and that is the whole product.
Your session expires, theirs has not: you redirect, the identity provider silently returns a new assertion, and the user sees a flicker. This is why an application can have a 30-minute session without annoying anyone — the re-authentication is invisible.
They log out of your application: unless you also end the identity provider's session (Chapter 8.4.4's RP-initiated logout), clicking login signs them straight back in. This is the single most common SSO support ticket, and the answer is that logout is per-session and there are three of them.
The auth_time claim, or SAML's AuthnInstant, is how you enforce freshness. For a sensitive action, check when the user actually authenticated, and if it was too long ago, force re-authentication with prompt=login (OIDC) or ForceAuthn (SAML). Without that, an eight-hour identity provider session means an eight-hour-old authentication is silently accepted for a payment change.
2. Federation
Federation is trust between organisations: your application accepts identities asserted by a customer's identity provider, whose users you have never seen.
The trust is concrete, not abstract. It is a configured issuer, a signing certificate or JWKS URL, and a set of endpoints. Nothing more — which is why "add a new customer's SSO" is a configuration record and why getting that record right is the whole integration.
Two shapes:
Direct federation — you configure each customer's identity provider yourself. Full control; the work grows with the number of customers, and each one is a support relationship.
Brokered federation — you integrate once with an identity broker (Auth0, Okta, Entra External ID, Keycloak), and it handles the many upstream providers. You get one protocol to implement, and the broker sits in your critical login path with its own outages, costs and limits. For a business-facing product this is usually the right trade, and it should be a deliberate decision rather than a default.
Multi-tenant design is the part that has a security requirement, and it is easy to get wrong:
- Each tenant maps to one (or more) identity provider configurations.
- You must verify domain ownership before letting a tenant claim an email domain. Otherwise a malicious customer registers
@bigcorp.com, and every BigCorp user routed by domain lands in their tenant — or worse, their identity provider can assert identities for BigCorp users. Verify by DNS TXT record, exactly as in Chapter 5.5. - Assertions must be scoped: an assertion from tenant A's identity provider may only create or match users in tenant A. A missing tenant check here is a cross-tenant account takeover.
Home realm discovery is how you route a user to the right identity provider. Ask for the email address first, look up the domain, and redirect. If the domain is unknown, fall back to a password form. This is why enterprise login pages ask for email, then move to a second screen — the second screen depends on the answer to the first.
3. Provisioning: JIT and its gap
Just-in-time provisioning: on first successful login, create the user from the assertion's attributes.
ts
async function onAssertion(tenantId: string, claims: Claims) {
let user = await findByFederatedId(tenantId, claims.iss, claims.sub); // (1)
if (!user) {
user = await createUser({
tenantId, // (2)
federatedIss: claims.iss, federatedSub: claims.sub,
email: claims.email, name: claims.name,
roles: mapGroupsToRoles(claims.groups ?? []), // (3)
});
} else {
await updateUser(user.id, { email: claims.email, name: claims.name,
roles: mapGroupsToRoles(claims.groups ?? []) }); // (4)
}
return user;
}(1) Identity is (tenant, issuer, subject) — never email, for the reasons in Chapter 8.4.4, and with the tenant included so an assertion cannot reach across tenants. (2) The tenant comes from which identity provider configuration validated the assertion, never from the assertion's own content. (3) Group names from the customer's directory mapped to your application's roles, through a configurable mapping. (4) Refresh attributes on every login, or a user promoted last month keeps last month's roles.
JIT is simple and has one structural hole: it only ever runs when someone logs in.
- A dismissed employee is never removed, because they never log in again. They keep their role, appear in mention lists, own shared resources, and still hold a valid session until it expires.
- A user who loses a group has stale roles until their next login.
- Nobody exists in your system until they first log in, so an administrator cannot pre-assign anything.
The identity provider stops issuing assertions, which does stop new logins. That is a real control and it is not deprovisioning: the account, its data and its permissions remain.
4. SCIM
SCIM (System for Cross-domain Identity Management, RFC 7643/7644) is the standard for the identity provider to push user lifecycle events to you.
Two resource types and standard operations:
POST /scim/v2/Users create
GET /scim/v2/Users/{id} read
GET /scim/v2/Users?filter=userName eq "ana@customer.com"
PATCH /scim/v2/Users/{id} partial update ← the one that matters
DELETE /scim/v2/Users/{id} remove
/scim/v2/Groups the same, for groups and membershipImplementation details that decide whether it works with real identity providers:
Deactivation usually arrives as PATCH with active: false, not DELETE. Most providers never send DELETE. Treat active: false as a full deactivation: end sessions, revoke tokens, block login. Treating it as a soft flag while leaving sessions alive is the most common SCIM mistake, and it silently reproduces the exact problem SCIM was adopted to solve.
id is yours, externalId is theirs. Return your identifier as id and store their externalId; they will use both.
Support PATCH properly. Group membership updates arrive as PATCH operations with add and remove on the members path, and a provider that finds PATCH unsupported will fall back to replacing entire groups — which is slow and occasionally destructive.
Support filtering on userName and pagination with startIndex and count. Providers reconcile by listing everything.
Be idempotent and tolerant. Providers retry, send duplicates, and reorder. Creating a user who already exists should return the existing one (409 with a body they can use), not create a second.
Authenticate the SCIM endpoint with a long-lived bearer token per tenant, treat it as a high-value credential — it can create administrators — and support rotation.
The pairing to state clearly: SCIM for lifecycle, SAML or OIDC for authentication. They are different jobs. SCIM is what makes offboarding immediate, and immediate offboarding is what auditors ask about.
5. Mapping groups to roles
The customer's directory has groups like CN=Finance-Approvers,OU=Groups,DC=customer,DC=com. Your application has roles like approver.
Make the mapping configuration, per tenant, editable by the customer's administrator. Hardcoding group names is the single most common cause of a customer being unable to complete their own SSO setup.
Rules that save trouble later:
- Deny by default. An unmapped group grants nothing.
- Refresh roles on every login and on every SCIM update, so removal actually takes effect.
- Keep at least one role assignable inside your application, so a broken mapping does not lock every administrator out.
- Log the raw groups received. Same reasoning as Chapter 8.4.5's attribute names: it turns a multi-day ticket into a configuration change.
Nested groups are a real trap. A user in Managers, which is a member of Employees, may or may not appear as a member of Employees in the assertion depending on the provider's configuration. Ask; do not assume.
6. SAML or OIDC
| SAML 2.0 | OIDC | |
|---|---|---|
| Format | XML | JSON / JWT |
| Age | 2005 | 2014 |
| Mobile and SPA | Poor | Designed for it |
| Implementation risk | High (canonicalisation, wrapping) | Lower |
| Enterprise support | Universal | Now broad |
| Provisioning | SCIM alongside | SCIM alongside |
| API authorization | Not its job | Same token family |
Choose OIDC for anything new. Choose SAML when the customer requires it — and business customers still do, particularly where the identity team's tooling and audit processes were built around it.
In practice a business-facing product supports both, and the sensible architecture is one internal identity model with two adapters, so the rest of the application never knows which protocol was used.
7. What SSO concentrates
SSO makes the identity provider a single point of compromise. One phished set of credentials, if not protected by a second factor, opens every application at once. So the security of your customers' logins is now mostly outside your control, and the things that matter are the ones you can require or check:
- MFA at the identity provider (Chapter 8.4.7) — this is the control that matters most, and it is theirs to enable.
- Conditional access — the identity provider can require a managed device or a known location. Chapter 8.4.9 covers Entra's version.
- Session lifetime, and
auth_timechecks for sensitive actions. - A break-glass account — at least one administrator account that does not depend on SSO, with a long stored password and MFA, for the day the identity provider is unreachable. An outage at the identity provider otherwise locks everyone out with no way in, including the people who would fix it. This is a real incident that happens, and the account must be monitored precisely because it bypasses the normal path.
And the enterprise readiness checklist buyers actually send, which is worth knowing before it arrives: SAML 2.0 and OIDC support, SCIM provisioning, per-tenant configuration by the customer's own administrator, role mapping from groups, audit logs of authentication and administrative actions, session timeout configuration, and IP allow-listing. Support these and enterprise deals proceed; miss provisioning and audit logs and they stall.
What the interviewer will push on
"How does SSO work end to end?" Three sessions — identity provider, application, browser — and the second application redirects, finds an existing identity provider session, and gets an assertion with no prompt. Every confusing behaviour, including logout appearing not to work, follows from those lifetimes being independent.
"What is JIT provisioning and what is wrong with it?" Create the user from the assertion on first login. It never deprovisions, because a departed employee never logs in again — so their account, roles and shared resources persist. The identity provider refusing new assertions is a control, not deprovisioning. SCIM is the fix.
"What does SCIM do and what would you watch out for implementing it?" Push-based lifecycle from the identity provider. The details that decide success: deactivation arrives as PATCH active:false, not DELETE, and must end sessions and revoke tokens; support PATCH for group membership; be idempotent under retries; and treat the SCIM token as a high-value credential because it can create administrators.
"A customer wants SSO for their @bigcorp.com domain. What do you check?" Domain ownership, by DNS TXT record, before routing anything. Otherwise a malicious tenant claims another company's domain and receives — or asserts — their users. Then the second control: an assertion from one tenant's identity provider may only touch that tenant's users.
"SAML or OIDC?" OIDC for anything new; SAML because enterprise customers require it. Then the architectural answer: one internal identity model with two adapters, so nothing downstream knows which protocol authenticated the user.
"What new risk does SSO introduce?" Concentration: one compromised identity provider account opens everything, and identity provider availability becomes your availability. Name the mitigations you control — auth_time re-authentication for sensitive actions, session lifetime — and the one everybody forgets: a break-glass administrator account that does not depend on SSO, monitored precisely because it bypasses it.
One thing to volunteer: point out that logging the raw groups and attribute names received during onboarding is what turns SSO integration from a multi-day support thread into a five-minute configuration change. Every identity provider names things differently, and the customer's administrator usually cannot tell you what they send.
Recall
- SSO is three independent sessions — identity provider, application, browser. Silent re-authentication, and logout appearing not to work, both follow directly from that.
- Use
auth_time(or SAML'sAuthnInstant) to force re-authentication for sensitive actions; otherwise an eight-hour-old login is silently accepted. - Federation trust is a concrete record: issuer, signing key, endpoints. Direct federation scales with customers; a broker trades control for one integration and puts a third party in your login path.
- Multi-tenant rules: verify domain ownership by DNS TXT before routing a domain, and scope every assertion to the tenant whose configuration validated it. Missing either is cross-tenant account takeover.
- JIT provisioning creates users from the assertion and never deprovisions, because a departed employee never logs in again. Refresh attributes and roles on every login.
- SCIM is the lifecycle fix. Deactivation arrives as
PATCH active:false, notDELETE— end sessions and revoke tokens on it. SupportPATCHfor group membership, filtering, pagination, and be idempotent under retries. The SCIM token can create administrators. - Group-to-role mapping is per-tenant configuration, deny by default, refreshed on every login, with one role assignable inside your app so a broken mapping cannot lock out every administrator. Log the raw groups received.
- SSO concentrates risk: MFA at the identity provider is the control that matters most, and a break-glass account outside SSO is what saves you when the identity provider is down.
Self-test: Why does logging out of your app not log the user out? · What does JIT provisioning never do, and what happens as a result? · Which SCIM operation actually means "this person left"? · What attack does domain verification prevent? · Why keep one role assignable outside the group mapping? · Why is a break-glass account both necessary and dangerous?
Next: 8.4.7 covers the control that matters more than everything else on these pages — second factors, how TOTP works offline, and why passkeys are the first mechanism that actually beats phishing.