Aller au contenu principal
All articles
Security

Enterprise SSO: 3 months to find the right stack

A project post-mortem: choosing a B2B SSO stack in 2026. Auth0, WorkOS, Clerk, Supabase, and Keycloak compared — and why WorkOS won for our context.

VALRY LABS Security TeamApril 14, 202612 min read

The requirements imposed by enterprise clients

It all started with a tender we didn't lose — but could have lost on a single word: SSO. The end client, a regulated French player with 4,000 employees, wanted federated authentication activated before signature. Three protocols were cited in black and white in the security annexes: SAML 2.0, OIDC, and SCIM for provisioning. None was optional.

SAML 2.0 remains the reference in the Fortune 500 in 2026. Azure AD, Okta, and Google Workspace are the three IdPs we encounter in 90% of our B2B deals, and they all expose a legacy SAML endpoint. Refusing SAML means closing yourself off to half of enterprise accounts. OIDC is the modern version: stateless, JSON-based, simpler to integrate on the browser side, and expected by IT departments that have migrated to recent IdPs.

SCIM was the real blocking point. CISOs now demand an automated user lifecycle: when an employee arrives, the account creates itself; when they leave, it deactivates immediately. The SCIM 2.0 `/Users` endpoint has become a de facto standard. Add to that the enforcement of an MFA policy on sensitive accounts, and the production of exportable audit logs (logins, failures, role changes) for the client's internal controls.

Summary of the requirements, validated with the client: SAML + OIDC as entry points, SCIM 2.0 for provisioning, MFA configurable per organisation, and a secured audit-log stream. No consumer-grade solution checks these four boxes in freemium mode. We had to pick a dedicated vendor, or accept building the whole thing ourselves.

The evaluation matrix we used

Five vendors were shortlisted. Each was scored on six criteria: protocol coverage (SAML, OIDC, SCIM), monthly cost for 50 organisations and 5,000 users, developer documentation quality, estimated maintenance load, ease of integration into a Next.js 15 stack, and audit-log depth. The figures below were collected between January and March 2026, directly from public pricing pages.

Auth0 (Okta) remains the maturity reference: rules and Actions let you do everything, from token enrichment to contextual multi-factor auth. The flip side: enterprise SSO (SAML + OIDC) is only available from the B2B Enterprise plan, around $2,300/month for our volume, excluding premium MFA. The learning curve on Actions is real, and debugging in production remains a sport.

WorkOS positions SSO + Directory Sync as a first-class service. Pricing is readable: $125/month base, then a per-MAU user cost that stays controlled. The API is clean, the documentation developer-oriented, and the Next.js-side adapter fits in about a hundred lines. SCIM is supported natively via Directory Sync.

Supabase Auth, with its SSO add-on, is tempting for a team already on Supabase: everything stays in the same console. But SAML SSO is only available on the Enterprise tier (from $2,500/month, by quote), and SCIM is not yet first-class. For a pure B2B SaaS, that is a serious brake. Clerk offers an excellent DX and a well-designed organisation system: SAML SSO is unlocked on the Premium plan (public catalogue around $0.02/extra MAU), but the opinionated philosophy becomes an obstacle as soon as you step off the beaten path.

Finally, self-hosted Keycloak was the free and complete option: zero licence cost, native support for SAML, OIDC, and SCIM, and a powerful admin console. The price to pay is operational: it is a JVM stack to maintain, updates to track, PostgreSQL dumps to back up, and configuration complexity that easily consumes 20 hours per month of one engineer. Over three years, the hidden cost far exceeds WorkOS pricing.

Three months of work, month by month

Month 1 was devoted to the shortlist and the POC. For each vendor, we stood up an isolated environment, connected a test Azure AD, and ran three journeys: service-provider-initiated login (SP-initiated), IdP-initiated login, and user provisioning via SCIM. This first filter eliminated Supabase Auth (no genuinely usable SCIM within our window) and Clerk (Premium SSO did not seem to meet the end client's bar on audit logs).

Month 2 was the real integration into the product, with a single retained vendor: WorkOS. We set up the unified callback, connected the adapter on the Next.js 15 side, and ran a load campaign. The key test: 500 simultaneous connections on the `/auth/callback` endpoint to verify there was neither a deadlock on the sessions table nor degradation on the WorkOS side. The bottleneck was not the vendor but our own PostgreSQL database on `audit_logs` writes. We moved those writes to an async queue (Redis + dedicated worker) before month 3.

Month 3 was the beta with two design partners: a French consulting firm (Azure AD, ~1,200 users) and an industrial scale-up (Okta, ~600 users). Their feedback confirmed two points: SP-initiated works flawlessly on both IdPs, and IdP-initiated requires specific handling (managing a non-empty `RelayState`, and post-authentication redirection to a neutral page). The beta also revealed that client-side SAML signing-certificate rotation was not anticipated by our operations — a point we corrected before the general production rollout.

The hidden costs of “enterprise SSO”

The first hidden cost is the SAML metadata exchange. On paper, it is an XML file to exchange once. In practice, clients rotate their signing certificates every 12 to 24 months, sometimes without warning, and the day it breaks, it is a total login outage for the entire organisation concerned. You need a runbook, an alert on the remote certificate's expiry date (visible in the metadata), and an identified point of contact at each enterprise client.

The second trap is the JIT vs SCIM opposition. JIT (Just-In-Time provisioning) creates the user on the fly at first SAML/OIDC login. It is simple, elegant, and it breaks the day a user changes email or name in the IdP: JIT does not update, it creates a duplicate. SCIM is more robust because it synchronises upstream, but it requires rigorous attribute mapping (`userName`, `emails`, `displayName`, `active`) that varies by IdP. For a product aiming at multiple enterprise clients, SCIM is non-negotiable; JIT is an acceptable bonus for very small businesses.

The third hidden cost concerns IdP quirks. Azure AD, Okta, and Google Workspace all claim to implement SAML 2.0, but behave differently on three points: the `NameId` format (email, persistent, unspecified), the handling of `RelayState` (limited to 80 characters on Okta), and whether or not user attributes are sent in the assertion. You need a compatibility table per IdP, and a dedicated test procedure. We ended up maintaining an internal Azure AD / Okta / Google / OneLogin / JumpCloud matrix, updated with every new client.

Last point, often forgotten: billing. SSO vendors bill per MAU or per organisation. A client with 5,000 users of whom only 800 are monthly active can cost twice as much as expected if the granularity is wrong. Read the pricing in detail before signing, and project over 24 months with a 30% user-growth margin.

The final choice for our context

Our decision, documented in an internal ADR: WorkOS. Three criteria made the difference. First, cost: for a product under €500k ARR, WorkOS pricing ($125/month base + per-user) stays predictable, where Auth0 Enterprise and Clerk Premium would have pushed us over a significant cost threshold from the tenth organisation onwards. Next, protocol coverage is broad: SAML, OIDC, and SCIM Directory Sync are included with no separate add-on. Finally, the developer experience was above the rest: code-oriented documentation, a well-typed TypeScript SDK, and a test cycle that fits in a single day.

This choice is not universal. For a startup under €100k ARR with a single expected enterprise client, Clerk or even Supabase Auth with the SSO add-on can suffice, and the initial cost will be lower. For a scale-up beyond €2M ARR with very heavy audit needs and a dedicated platform team, Auth0 or Keycloak can become relevant: Auth0 for the ecosystem, Keycloak for total control. Conversely, below €500k ARR and for a B2B SaaS with several enterprise clients, WorkOS remains our default recommendation in 2026.

We kept the reversibility criterion: the WorkOS contract is terminable with 30 days' notice, and SAML metadata remains manually exchangeable in case of a switch to another vendor. The dependency stays contained, and a migration ADR is already sketched out in case the volume flips the economic balance in 18 to 24 months.

Implementation lessons: sessions, callbacks, and flows

First topic: stateless JWT vs reference sessions. Stateless JWT is seductive because it avoids a database round trip on every request. But in enterprise B2B, the need for revocation is real: a compromised account at a client must be deactivatable instantly, not at the next refresh token. We chose reference sessions stored in the database, checked on every request through a Redis cache (60-second TTL). Acceptable overhead, and guaranteed immediate revocation.

Second topic: the `redirect_uri` trap. SAML and OIDC require a callback declared on the IdP side. With several identity providers (Azure AD, Okta, Google), it is tempting to declare one `redirect_uri` per IdP (`/auth/callback/azure`, `/auth/callback/okta`, etc.). That is a mistake. The best practice is a single universal `/auth/callback`, with dispatch via the `state` parameter (OIDC) or `RelayState` (SAML). This simplifies IdP configuration on the client side and centralises routing logic on the application side.

Third topic: SP-initiated and IdP-initiated flows. SP-initiated (the user clicks "Sign in with SSO" on your site, you redirect to the IdP) is the default flow, and the simplest. IdP-initiated (the user clicks your app's icon in the IdP portal, the assertion arrives with no prior call) is requested by a majority of enterprise clients. Both must work. The key difference: in IdP-initiated, there is no previously stored `state`, so you must handle a potentially empty `RelayState` and redirect intelligently to the home page (not to the originally requested URL, which does not exist).

Fourth topic, cross-cutting: signed SAML assertions must be verified rigorously. Verifying the message signature is not enough; you must also verify the signature of the inner assertion, check the `destination`, the `recipient`, the validity window (`NotBefore` / `NotOnOrAfter`), and reject any assertion received outside its time window. On this point, relying on a proven library (WorkOS handles it for us; in Keycloak it is native) rather than a homegrown parser is non-negotiable.

What we would do differently

Three changes if we had to redo this project tomorrow. First, we would have launched the design-partner beta in month 2 rather than month 3. The feedback on IdP-initiated and on SAML certificate rotation arrived too late in the cycle, and consumed two weeks of rework. A design partner accessible from the initial POC surfaces these gaps when the cost of correction is still low.

Second, we would have written the IdP compatibility table before choosing the vendor, not after. The Azure AD / Okta / Google / OneLogin / JumpCloud matrix has become a valuable internal asset, but it was produced reactively. It is now a mandatory deliverable of any SSO project, before even the technical choice.

Third, we would have over-provisioned the SSO operations runbook from the start. Today it covers certificate rotation, attribute updates, adding a new IdP, and the procedure for unblocking a locked-out user. It was written after two minor production incidents. Next time, it will be written before the production rollout, validated with the client, and tested in a pre-production environment.

Key takeaways

Key points.

  • The 2026 enterprise requirements demand SAML 2.0 + OIDC + SCIM 2.0: none of the three is optional if you aim at the Fortune 500.
  • For a B2B SaaS under €500k ARR, WorkOS ($125/mo base + per-user) offers the best cost / DX / protocol-coverage ratio in 2026.
  • Auth0 remains the maturity reference (~$2,300/mo for enterprise SSO), Clerk Premium for DX, Keycloak for total control at ~20h/month of ops.
  • The hidden costs of enterprise SSO: SAML certificate rotation, JIT vs SCIM (prefer SCIM for multi-client), per-IdP quirks (Azure AD, Okta, Google).
  • On implementation: reference sessions for revocation, a single callback dispatched by state/RelayState, and both SP-initiated AND IdP-initiated must work.
SSOSAMLOIDCWorkOSB2BAuth