What the clause actually asks you to hand over
The clause is usually one line in a security schedule. The platform must support SAML 2.0 single sign-on and SCIM user provisioning. Sized as a feature it reads as an authentication library and a new button on the login page. What the buyer is asking is that their identity team becomes the authority over who holds an account in your product, what that account can do, and how quickly it stops working. Your product answers all three questions today, and moving those answers to an external directory changes your authorisation model.
Federation puts two account models alongside each other. There are the local accounts your product created, with passwords, invitations and roles granted in your own admin screen, and there is the customer's directory, which believes it holds the truth about employment. Every design decision that follows settles which one wins in a specific case: a user who signed up with a work email before single sign-on was configured, a password that still works after enforcement, a person removed from the directory who still holds an API token your product issued.
The security work does not sit in the login flow, where a library gives you the redirect out, the assertion back and the session created. Account takeover in federated products comes from what the service provider fails to check when the assertion arrives, and from the credentials that keep working after the customer believes federation is mandatory. Functional testing sees neither, because functional tests send well-formed assertions from the right tenant and confirm that the user lands on the dashboard.
Should you build SAML, OpenID Connect, or both?
OpenID Connect, which most engineering teams call OIDC, is the easier protocol to implement correctly and the place to start if the buyer will accept it. It sits on OAuth 2.0, so the flow is an authorisation code exchange with PKCE, the issuer publishes a discovery document carrying its endpoints and signing keys, and the identity assertion is an ID token in signed JWT form. Validation is a short, well-defined list: verify the signature against the issuer's published keys, confirm the issuer matches exactly, confirm the audience contains your client identifier, check expiry, and confirm the nonce equals the one you sent.
SAML 2.0 is what a corporate identity team will hand you anyway. It has been an OASIS standard since March 2005, it carries assertions as XML signed with XML Signature and delivered through a browser POST, and the identity team's runbook, metadata exchange and change process are already built around it. Declining rarely moves the buyer, because the request comes from a team applying one onboarding pattern to hundreds of applications.
Build the two as edge adapters over one internal shape. Each protocol handler does its own validation and produces the same object: the issuer, a stable subject identifier, an email address, the tenant that was resolved, and whatever group claims arrived. Session creation, provisioning and role assignment read only that object and never touch a raw assertion. Keeping the protocol-specific checks strictly at the edge stops a fix applied to one path from silently missing the other.
Choose the identifier you key accounts to before you write either adapter. OpenID Connect defines the subject claim as locally unique within the issuer and never reassigned, which is the guarantee an account record needs, and it tells relying parties not to depend on the email claim being unique. The persistent name identifier format plays the same role in SAML. Store issuer plus subject as the link to the directory, and treat the email address as a mutable attribute that can change without meaning a different person.
Verify the domain before you federate anything
Federation only means something if your system already holds one verified fact: this tenant controls this email domain. Without it, the link between an email address and a workspace is an assumption, and every feature built on that assumption becomes an access control defect. Two depend on it directly. Automatic joining puts a new user with a known domain into the right workspace, and enforcement refuses the password path to a user at that domain.
The mechanism is a DNS check. The tenant administrator adds a TXT record carrying a value your system generated for that tenant, you resolve it from your own infrastructure, and you record the verification with its timestamp and the exact value observed. Generate a distinct value per tenant per domain so a published record cannot be reused by a second claimant, and re-verify on a schedule. If the record disappears, the connection needs review, because a domain that verified once is not a domain that still resolves to the same owner.
Decide the conflict rules before a customer hits them. A domain claimed by two tenants is normal in a group of companies, and a subdomain claimed separately from its parent is normal in larger organisations. Pick whether a domain can be verified once globally or once per tenant, and make claiming it an explicit administrative action with an audit entry, never something your code infers from a signup address. That closes the worst defect in multi-tenant single sign-on, where an administrator points a tenant at an identity provider for a domain they do not control, converting control of any identity provider into control of somebody else's workspace.
The assertion checks that stop one customer becoming another
Under the HTTP POST binding, the web browser single sign-on profile requires the enclosed assertions themselves to be signed. A signature over the response element alone does not satisfy that, and the recurring bug is a validator that asks whether the document contains a valid signature instead of whether the element it read claims from is the element that signature covered. That gap is XML signature wrapping: an attacker takes an assertion legitimately signed for their own account, relocates it inside the document, and adds an unsigned assertion naming a different subject for the application to parse. Resolve the signature reference to a node, extract claims from that node only, and reject any response carrying more than one assertion.
The profile's response processing rules supply the rest, and each item binds the assertion along a different axis. The Recipient attribute in the bearer subject confirmation must match the assertion consumer service URL the response was delivered to. Its NotOnOrAfter must not have passed, subject to allowable clock skew. The InResponseTo value must equal the identifier of the authentication request you issued. The assertion must carry an audience restriction naming your entity identifier. The POST-specific rules also require you to prevent replay by keeping the set of used assertion identifiers for as long as the assertion would be considered valid, which means a shared store the moment you run more than one instance.
The check that goes missing most often is not in that list, because the profile was not written with a multi-tenant service provider in mind. A valid signature tells you that some configured identity provider signed the assertion. It does not tell you which customer it belongs to. If you verify against a pool holding every certificate every customer has uploaded, any customer's identity team can mint an assertion for any other customer's user and your validator will accept it. Resolve the tenant first, from a per-tenant assertion consumer service URL, verify against that tenant's configured certificate and no other, then confirm the subject's email domain is one that tenant has verified. All three have to agree before a session is created.
Two further items deserve their own tests. Signature validation canonicalises the XML, which strips comments, while some XML APIs return only the first text node when the application asks for a value, so an inserted comment can split a name identifier and leave the application reading a shorter string than the signature covered. That class was disclosed publicly against several widely used SAML libraries, which makes keeping the library current part of the control. Identity-provider-initiated sign-in is the other: the profile states that an unsolicited response must not carry an InResponseTo attribute, so supporting that flow removes the check binding the assertion to a request you made, and a delivered assertion can quietly sign a victim into an attacker's account. Keep it on a separate endpoint if a customer requires it, confirm the identity being signed in instead of replacing an existing session in the background, and never let that flow complete an account link or a credential change.
| Check | What it binds | What it stops |
|---|---|---|
| The signature reference resolves to the element you read claims from | Claims to signature | XML signature wrapping, where an unsigned assertion rides inside a validly signed document |
| The verifying key is the one certificate this tenant configured | Assertion to tenant | One customer's identity team authenticating as another customer's user |
| Audience restriction names your entity identifier | Assertion to service | An assertion minted for a different application being presented at yours |
| Recipient matches the assertion consumer service URL it arrived at | Assertion to endpoint | Delivery to a secondary or legacy endpoint with weaker checks |
| InResponseTo matches an authentication request you issued | Assertion to request | Login cross-site request forgery through an unsolicited response |
| NotOnOrAfter plus a shared store of consumed assertion identifiers | Assertion to one use | Replay of a captured assertion from a proxy log or browser history |
Assertion validation checks and what each one prevents
How fast does access disappear when someone leaves?
That is the question underneath the provisioning requirement, and the one a buyer's security team presses hardest. Just-in-time creation from the assertion answers the joining half well, because the first successful sign-in creates the account with the attributes the directory sent. Nothing in a login flow can tell you about a person who has stopped logging in, so a product that does just-in-time creation and nothing else has automated onboarding and left offboarding to a support ticket. That is why the clause names provisioning separately from single sign-on.
SCIM is the standard they mean. RFC 7644 defines the protocol and RFC 7643 defines the schema, and your side is a set of REST endpoints the customer's directory calls as a client. You expose /Users and /Groups, support GET with a filter so the directory can find an existing record by userName, POST to create, PATCH for partial updates using add, remove and replace operations, PUT for replacement, and DELETE. What each call means inside your product is the half that takes a decision.
RFC 7643 defines the active attribute as a Boolean indicating the user's administrative status, then states that its definitive meaning is determined by the service provider. Directories differ in what they send when someone leaves: a PATCH setting active to false, a DELETE, or nothing more than the removal of a group assignment. Handle all three and make them converge on one internal outcome, which is sessions terminated, issued tokens revoked, no remaining login path, the record retained for audit and billing, and a documented answer on whether the seat still counts against the subscription.
Match incoming SCIM records on externalId or on the same stable subject identifier your single sign-on adapter keys to, never on the email address, because the first name change or domain migration will create duplicates or attach a record to the wrong person. Define up front what happens to a user created just in time before SCIM was configured, and only link automatically where the domain is verified. Treat the SCIM credential as a high-value asset, because it is a long-lived bearer token that can create and modify accounts inside a customer's workspace. Scope it to one tenant, support rotation without an outage, and log every call to the same audit trail as any other administrative action.
Mapping groups to roles without becoming their helpdesk
Group membership arrives either as a multi-valued attribute in the assertion or as SCIM group membership, and what the customer wants from it is to administer access from their directory instead of raising tickets with your support team. The surface you owe them is a tenant-scoped mapping from an external group to a role in your product, editable by their own administrator, with unmapped groups defaulting to no access. It needs one mapping table and a clear rule about which side wins, and it does not change how roles work inside the product.
Key the mapping to the group's stable identifier rather than its display name. Directory groups get renamed and restructured constantly, and a mapping keyed on a name will silently strip access from everyone in a renamed group, or grant access when a different group inherits the name that was freed up. Store the display name so the administrator can recognise what they are mapping, refresh it on each sync, and never let it carry authority over an access decision.
Decide precedence explicitly and put it in your documentation. If the directory is authoritative, removing a user from a group removes the role at the next sync or the next sign-in, and any grant made inside your product is temporary. If your model is additive, in-product grants persist and the directory can only add. Buyers assume the first and many products implement the second, and that difference is the whole value of the feature to the person signing the contract. Keep at least one local administrator a directory sync cannot remove, so a bad mapping cannot lock every administrator out of a tenant, and make every mapping change an audit entry carrying the actor, the previous value and the new value.
The bypasses left behind after enforcement
Enforcement is a claim about every authentication path, and products usually enforce on one. The login form refuses the password, while the password reset flow does not run through the login form and quietly re-establishes a local credential for a user who is supposed to be federated. The same gap appears on a legacy mobile endpoint, on a basic authentication path kept alive for an old integration, and on an invitation link issued before enforcement was switched on. Enforcement has to be a check on the user's tenant applied everywhere a session can be created, so enumerate those places and test each one.
The other half is what a user leaves behind inside your product. None of it is reachable from the customer's own offboarding runbook, because these objects live in your database under your identifiers and only your deactivation path can revoke them. Build the inventory during design, from the list of things a user account is allowed to create, and make deactivation walk it in one transaction.
- Personal API tokens and access keys the user minted are bound to the user but stored independently of sessions, so revoking them belongs in the deactivation path, because a nightly job leaves a working credential overnight.
- Existing sessions must be terminated server side, and stateless tokens with a long expiry need either a revocation check on each request or a short expiry with a refresh path you can cut.
- OAuth grants the user made to third-party tools keep working under their identity and no directory event will mention them, so list them against the owning account and revoke them with it.
- Webhook secrets, deployment keys and integration credentials created by a leaver have no directory identity at all, so each needs a named owner and a screen the customer's administrator can see.
- Support impersonation paths and service accounts nobody mapped to a person survive every deprovisioning event you build, so they need their own review cadence and their own audit entries.
- A person who belongs to two of your customers' workspaces will be deprovisioned from one and stay in the other, so a deactivation must be scoped to the tenant that sent it and never applied globally by email address.
What to show a buyer's security team
The questionnaire asks whether you support SAML and SCIM, and the answer is a checkbox. The security review that follows a large deal asks how, and that answer has to be evidence. Prepare four things: the validation checks you perform on an inbound assertion and where in the code they happen, how an assertion is bound to exactly one tenant, what happens and how quickly when the directory marks a user inactive, and how the SCIM credential is scoped, rotated and logged.
Prove the checks with tests that try to break them. Build a fixture set that sends an assertion signed with another tenant's certificate, a document with a second unsigned assertion added, an assertion carrying the wrong audience, one whose validity window has passed, a replay of an identifier already consumed, and an unsolicited response with no matching request. Each must be refused, and each refusal must be logged with a reason. Run the set in continuous integration, because the federation adapter is exactly the kind of code that gets tidied up by someone who does not know why a check is there.
The reason a buyer presses on timing is that they stay accountable for the personal information they hold. Australian Privacy Principle 11 under the Privacy Act 1988 requires them to take reasonable steps to protect it, and a former employee holding a live session in your product is their exposure. Where they suspect an eligible data breach, section 26WH requires them to take all reasonable steps to complete an assessment within 30 days, and an eligible breach has to be notified to the OAIC and to the affected individuals. Deprovisioning latency in your product feeds that assessment, which is why an answer measured in minutes reads differently from an answer measured in the next nightly run.
We build and run production systems for Australian clients, and we review the same class of code inside other people's products. Two habits carry across all of it. Validation belongs at the edge, in one place a reviewer can find, so nobody has to prove a negative about every code path that can reach a session. Every change of authority, meaning a federation connection created, a domain verified, a group mapped to a role, belongs in an audit trail recording the actor, the action, and the state before and after. Our trust page sets out the rest of that posture.
