Custom Software With SSO and SCIM User Provisioning: The Real Cost
Adding SSO and SCIM provisioning to an existing product typically runs $15,000 to $60,000 over 4 to 10 weeks when your app already has a clean organization and membership model. Shipped as part of a first release it is $50,000 to $130,000 in 10 to 16 weeks. A full identity platform with group driven roles, audit evidence and certification against multiple identity providers is $150,000 to $350,000 over 6 to 12 months. The number moves on your data model, not on the protocols.
What this looks like the first time a real identity provider plugs in
SSO and SCIM get quoted as two checkboxes on an enterprise readiness list. SSO is an authentication protocol you have to verify cryptographically or it is a login bypass. SCIM is a lifecycle API that a third party you do not control calls against your database, on its own schedule, forever.
Here is a failure we have cleaned up more than once. A customer terminates an employee on a Friday. Their Microsoft Entra ID provisioning job sends your SCIM endpoint a PATCH: {"op":"replace","path":"active","value":"False"}. Notice that "False" is a JSON string, not a boolean. Some Entra provisioning jobs send it exactly that way. The naive handler does if (op.value) { user.active = true }, the string is truthy, and you have just reactivated the person who was fired four hours ago. Entra gets an HTTP 200. The job shows green in the customer's admin console. Nobody is paged. The terminated user keeps a live session and a valid refresh token until somebody notices. Three months later a SOC 2 auditor samples five terminations and asks for evidence of access revocation within 24 hours. That is the conversation.
Nothing about that bug is exotic. It is what happens when SCIM is treated as CRUD instead of as an integration with four clients that each read the same RFC differently.
SAML is the short part of the build and the long part of your risk
You will support two protocols. OIDC is straightforward: authorization code flow, discovery document, JWKS endpoint, done in days. SAML 2.0 is the one enterprises actually demand, and it is where the security work lives.
- Verify the XML signature over the correct element, and reject a response where the assertion is unsigned or where a second unsigned assertion has been smuggled in. Signature wrapping is not theoretical. Parser differential bugs that were full authentication bypasses were found in ruby-saml in 2024 and again in 2025.
- Enforce
Audience,Recipient,NotOnOrAfterandInResponseTo, keep a replay cache of assertion IDs, and allow about 60 seconds of clock skew because on premise ADFS servers drift. - Decide deliberately about IdP initiated SAML. It has no
InResponseTo, so it is a login CSRF vector by construction. Every enterprise buyer wants the Okta dashboard tile anyway. Pick a stance and write it down. - Handle signing certificate rotation. If you paste one PEM into a config row, you have scheduled an outage for whenever the customer's IdP rotates. Accept multiple valid certs and poll the federation metadata URL.
- Never trust the email claim across connections. If you look up users by email globally, anyone who can stand up their own Okta tenant can assert
cfo@yourbigcustomer.comand walk in. Bind every connection to verified domains and scope identities to an organization, not to an inbox.
Also pick your NameID format on day one. If you key accounts on emailAddress and the customer's IdP admin flips to persistent, every user in that org gets a brand new empty account on their next login.
SCIM 2.0 is three RFCs and four incompatible dialects
SCIM is RFC 7642, 7643 and 7644. Reading them takes a day. Discovering that Okta, Entra ID, Google Workspace and OneLogin each speak a different subset takes a quarter.
- PATCH dialects. The spec defines
add,replaceandremovewith an optionalpath. Okta commonly sendsreplacewith a value object and no path. Entra sends a path. Both are legal. You need a real PATCH engine, not a switch statement, and it must treat"True","False",trueandfalseas the same two values. - Filters. The grammar has
eq ne co sw ew pr gt ge lt leplusand or notand grouping. In practice you will seeuserName eq,externalId eqanddisplayName eqfor groups. Do not hand roll a parser that splits oneqand interpolates into SQL. Use a filter parser and map it onto a whitelist of columns. - Pagination.
startIndexis 1 based, not 0 based. This is the most common bug in first SCIM builds and it silently skips or duplicates one user per page. - Empty results. A filter matching nothing returns 200 with
totalResults: 0, not 404. Return 404 and Entra counts it as an error. - Errors. Error bodies must be SCIM shaped, with
schemas,statusandscimType. A duplicate create is 409 withscimType: "uniqueness". Identity providers parse this. Your app's generic JSON error makes them retry forever. - Idempotency. They retry. If POST /Users is not deduplicated on
externalId, one network blip gives you two accounts for one human, and group sync will fight over the duplicate for weeks.
Deprovisioning is the only part your customer's auditor reads
What does removal mean? Okta can be configured to send either DELETE /Users/{id} or a PATCH setting active to false, depending on whether the admin picked deactivate or delete. Entra normally sends the PATCH and, in most configurations, never sends a DELETE at all. Handle both, and do not honor DELETE literally. Hard deleting destroys the user's history, breaks foreign keys on every record they created, and removes the audit trail the auditor came for. Tombstone instead: mark inactive, keep the row, keep the external ID mapping so a rehire re-links rather than duplicates, and return 404 on the SCIM read afterwards so the IdP believes you.
Deactivation has to actually revoke. Setting a boolean is not revocation. The moment a user goes inactive you kill sessions, revoke refresh tokens, revoke personal access tokens and API keys they minted, and drop long lived sockets. Teams miss the API keys constantly.
Understand the latency you are promising. Okta pushes lifecycle events close to real time. Entra runs its provisioning job on an incremental cycle of roughly 40 minutes, and the first full sync of a large tenant can take hours. If your contract promises revocation within 15 minutes, SCIM alone cannot keep it for an Entra customer. Say that during the sales cycle, not after.
Then there is quarantine. If your SCIM endpoint fails a large share of requests for a sustained period, Microsoft quarantines the provisioning job, backs retries off to roughly once a day, and disables the job after about four weeks. One bad Friday deploy can silently stop provisioning for an entire customer, and they find out when a new hire cannot log in. Alert on SCIM error rate per connection, not aggregate 5xx.
Groups are a separate feature in both Okta and Entra, and both send only direct members. Neither expands nested groups. A customer who put everyone in a parent group will file a bug against you. And be blunt about the common shortcut: SAML JIT provisioning creates users on first login and never removes them. If you shipped JIT and called it provisioning, you have onboarding and no offboarding.
The expensive part is your user table, not the protocols
Most products key users on a globally unique email and attach them to a workspace. This breaks in three places at once.
You need a first class organization, a connection belonging to that organization, and an identity row per user per connection carrying external_id, the raw NameID or sub, and the source. Then the awkward case: a contractor in two customer organizations that both run SCIM. Org A deactivates them. If active lives on the user, you just locked them out of Org B, whose admin touched nothing. Membership state belongs on the membership, not the user.
Then linking. Forty people signed up with passwords over two years, the customer buys the enterprise plan, SSO goes on. Do you merge on verified email inside that connection's verified domains, or force a re-invite? Merging silently is how one person's account gets attached to another person's identity when an alias or shared mailbox is in play. If your app has no organization concept today, this is not an SSO project. It is a tenancy refactor with an SSO feature at the end.
Certification and test tenants, the calendar nobody budgets
You cannot test this with mocks. You need live tenants: an Okta developer org, an Entra tenant, Google Workspace if you sell there, and one on premise style IdP such as ADFS or Ping if you sell into finance or government. Each one you skip is a support ticket later. Listing is separate again: appearing in the Okta Integration Network means Okta reviews your app and requires you to pass their SCIM spec test suite, and the Entra gallery is its own review. Neither is hard engineering. Both are weeks sitting in someone else's queue, after the code is done.
What this costs and how long it takes
These bands are Digital Heroes delivery experience across more than 2,000 projects, not industry survey numbers.
A focused build inside an existing product runs $15,000 to $60,000. The bottom is a product that already has organizations, memberships and a clean role model and needs SAML plus OIDC plus SCIM Users for two identity providers. The top is that scope plus group to role mapping, migrating existing password users, and certification with Okta and Entra.
As part of a first release, where identity is designed in rather than retrofitted, budget $50,000 to $130,000 over 10 to 16 weeks. Building it into the schema on day one is genuinely cheaper than adding it later.
A full platform, meaning multi org identity, SCIM Users and Groups, group driven roles, session policy, per connection audit logging with exportable evidence and listings in both directories, is $150,000 to $350,000 over 6 to 12 months.
What drives this specific capability's number up, in order:
- No organization concept in the schema. The biggest multiplier by far. It turns a 4 week feature into a 12 week refactor of nearly every query.
- Group mapping against a custom permission model. Three IdP groups to three flat roles is a day. Nested groups to a resource scoped permission matrix is a project.
- Number of identity providers. The second costs about 60 percent of the first, the fourth another 30 percent, and it never reaches zero because vendors ship behavior changes.
- Existing users to migrate and link.
- Compliance evidence: immutable provisioning logs, retention, and an export an auditor accepts.
- Maintenance. Provisioning breakage is always urgent because it blocks a new hire.
When you should not build this
Most teams should not build SCIM. SSO is a three week problem with a known shape. SCIM is a multi year tail of other people's quirks.
Under roughly 20 enterprise connections, buy directory sync and usually buy SSO with it. WorkOS lists SSO and Directory Sync at $125 per connection per month each at the time of writing, so 20 connections with both is around $60,000 a year, roughly one build, with someone else absorbing every Entra behavior change. Auth0, Frontegg, Stytch and Descope sit in the same space on quote based enterprise pricing. If you must self host, BoxyHQ SAML Jackson is open source and does SAML and directory sync, and SSOReady is open source for SAML.
Check two traps first. AWS Cognito federates SAML and OIDC but is not a SCIM server, so it does not solve provisioning at all. Keycloak has no SCIM server built in either, only community extensions. Teams pick both thinking identity is handled and find out during their first enterprise deal.
Build it yourself when per connection cost crosses build plus maintenance, which in our experience is somewhere north of 100 connections, or when you have data residency or air gap requirements a vendor cannot meet, or when your permission model is unusual enough that you would write the hard half anyway.
One more thing worth asking. If exactly one customer is demanding SCIM, find out what their auditor actually needs. Sometimes the requirement is evidence of access revocation within 24 hours, and a nightly sync from their HR (Human Resources) system plus a termination webhook satisfies it. We have watched a $40,000 SCIM scope disappear in a 20 minute call with a customer's compliance lead.
How to brief and vet a developer for this
Brief with facts, not with the word SSO. Name the identity providers you must support and who asked for each. State whether you need Groups or only Users. State your revocation SLA in minutes. Say whether your database has an organizations table today and hand over the schema. Say how many password users exist. Say whether you need an Okta Integration Network listing and by when.
Then ask these and listen for specifics:
- "How do you handle a SCIM PATCH where active arrives as the string False?" Anyone who has shipped this has the scar.
- "Is startIndex 0 or 1 based?" One right answer, immediately, no hedging.
- "What do you return for a filter that matches nothing?" Should be 200 with totalResults 0, instantly.
- "What happens on DELETE /Users/{id}?" If they say they delete the user, they have never survived an audit.
- "How do you handle IdP signing certificate rotation?" Wrong: the customer emails us the new cert. Right: metadata URL polling and multiple valid certs.
- "What is your position on IdP initiated SAML?" A shipped engineer has an opinion about InResponseTo. A beginner does not know the flow exists.
- "A contractor is in two customer orgs. Org A deactivates them. What happens in Org B?" This tests the data model, which is where the money is.
- "What is Entra provisioning quarantine and how would we find out we are in it?" Tests whether they have operated this, not just built it.
- "Show me the error body you return for a duplicate create." They should say scimType uniqueness without looking it up.
If someone quotes SSO and SCIM as a two week fixed price without asking to see your users table, they are quoting the login button. The login button is not the project.
The evidence behind this guide
Independent findings on why this investment pays off. Every link goes to the primary source.
- The federal government spends about 80% of its IT budget on operations and maintenance of existing systems rather than on development or modernization, with many critical systems being decades old. Source: U.S. Government Accountability Office (GAO) (2025) →
- OECD research finds that digitalisation offers SMEs opportunities to improve performance, spur innovation, enhance productivity and compete more evenly with larger firms; it reports that increased use of online platforms produced significant multi-factor productivity gains in SME-heavy sectors such as hospitality and retail, while smaller firms lag in adoption due to skills, resource and financing gaps. Source: OECD (2021) →
- SMS reminders that stated the specific cost of the appointment to the health system reduced missed appointments in Trial One, with the DNA (did-not-attend) rate falling from 11.1% (control) to 8.4% (specific-costs message) - an odds ratio of 0.74 (95% CI 0.61-0.89), i.e. roughly a 24-26% relative reduction - at no additional cost. (Trial Two replicated this at an 8.2% DNA rate.). Source: PLOS ONE (Hallsworth et al.) (2015) →
- PMI's Pulse of the Profession research found organizations waste an average of roughly 9.9% of every dollar invested in projects due to poor performance - equivalent to about $1 million wasted every 20 seconds collectively worldwide. Source: Project Management Institute (PMI) (2018) →
Rohan advises mid-market and enterprise teams on ERP, CRM and custom software, and has led delivery on dozens of business-software builds.
Writes for Digital Heroes, shipping business software for 2,000+ brands across 55+ countries since 2017.