SSO Is Not a Flip Switch

When we started modernizing the LMS infrastructure, the first thing someone asked was: "Can we just add SSO to the LMS?"

It's a reasonable question. It sounds like a small ask — flip a switch, wire up a login button, done. But the moment you start pulling on that thread, you find out it's not a switch at all. It's a decision about where identity lives across every system you own, and getting it wrong means you end up with the same mess you started with, just with a single sign-on button in front of it.

The landscape we inherited

Before the redesign, we had multiple user databases that didn't know about each other. The LMS had its own user table. The lab had its own. Salesforce was the authoritative source of truth for customer and partner users, but nothing downstream actually trusted it. Each system maintained identity independently. In practice, that meant a lot of things broke silently. Someone changed companies in Salesforce, but their LMS profile didn't update because the LMS didn't know about the change.

The pain wasn't dramatic. It was death by a thousand small inconsistencies: mismatched email addresses, mismatched passwords, stale enrollments, audit headaches, and the ongoing cost of someone manually keeping everything synchronized when customers or partners ran into those issues.

The actual question

The right question wasn't "can we just add SSO." It was: who is the source of truth for user identity, and what should every other system trust?

That reframe matters because it forces you to think about the whole flow, not just the login screen. If you bolt SSO onto the LMS without answering that question, you might still have five systems maintaining their own user tables — they just share a login now. The sync problem doesn't go away. The complexity doesn't go away. It just moves somewhere less visible.

In our case, the answer was obvious: Salesforce. It was already where customer and partner information lived, it just wasn't being used across the board.

The architecture: Salesforce as the identity spine

The design was straightforward in concept. Salesforce Experience Cloud became the identity provider. When a user logs into the LMS or the lab environment, they authenticate through Salesforce via OIDC. The downstream systems don't maintain their own user tables for auth — they trust what Salesforce says about who someone is.

We landed on OIDC instead of SAML for a practical reason: at the time, the LMS had a limit on how many SAML integrations it could support, and that slot was already consumed by another integration. OIDC was available, worked fine for our use case, and honestly wasn't a hard tradeoff once we understood the constraint.

The flow looks like this:

  1. New external users are added to Salesforce (creating an Experience Cloud community user record)
  2. User navigates to the LMS and hits "Log in with Salesforce"
  3. LMS redirects to Salesforce OIDC, user authenticates
  4. Salesforce returns identity claims to the LMS
  5. LMS provisions or updates the user based on those claims
  6. Same flow applies to the lab environment

When someone leaves the organization and their Salesforce record is deactivated, they lose access to everything on next login. No manual cleanup required.

The gotcha: community users aren't uniquely keyed

Here's where the "just flip a switch" assumption really falls apart.

Everything in Salesforce is an object - Community Users are objects, Contacts are objects, Licensed Users are objects, etc. In the case of our Salesforce "Identity Provider", Community Users are derived from Contacts. Sounds easy right? Here's the tricky part - Contacts and Community Users change constantly: job changes, company acquisitions, email address updates. The problem I ran into was the lack of guardrails around modifications to Contact and Community User records after creation. The only consistent unique identifier across all the systems we wanted to federate (LMS, labs, knowledge base, etc.) was email address.

That's a common SSO assumption, and it's usually fine - until you discover that years of organic data entry left you with invalid addresses, duplicates, or mismatches between systems. The fix required manual review of the affected records. Not fun. But it also made visible a data quality problem that had existed quietly for years before SSO gave it consequences.

Legacy user migration

Before we could turn on SSO, we had to deal with the existing LMS user population — people who'd been in the system before Salesforce became the source of truth. The approach was two-pass. First, we disabled any LMS user account with a last login date more than two years old. Those users had almost certainly completed their required training and aged out of it — the two-year mark aligned with training & certification expiry cycles. If any of them needed access again, they could be re-provisioned through the new flow.

For everyone else, we matched existing LMS users to Salesforce community user records by email address. Where the match was clean, it was automated. Where it wasn't — the duplicate/mapping problem described above — it required manual resolution.

What about when Salesforce goes down?

The honest answer is: if Salesforce is down, new logins fail. That's the tradeoff you accept when you make one system the identity spine. We accepted it because Salesforce already had strong availability SLAs and the organization was deeply dependent on it for much more than just training access.

That said, there were break-glass accounts tied to a separate SSO — administrative accounts that could still get in if needed. And if absolutely necessary, an LMS admin could assign a password directly to a user's account. That's not a primary support path, but it exists for genuine emergencies. Active sessions survive an outage; only new logins are blocked.

Is it a perfect fallback? No. But it was appropriate for the risk level and the operational reality.

The downstream impact

Once the identity architecture was in place, a few things got dramatically simpler.

Access became automatic. Instead of someone manually adding users, they simply got access following the same familiar login flow they already used for other systems.

Offboarding became a non-event. Deactivate in Salesforce (or move to a profile without access), access goes away. No checklist. No support ticket. No "we forgot to revoke their LMS access."

Auditing got easy. Every access was traceable back to a Salesforce identity. The audit trail was automatic, not assembled after the fact.

What I'd do differently

A few things I'd handle differently if I started over:

Nail down the unique key earlier. The community user duplicate problem cost us time we didn't expect to spend. Before any migration work starts, audit the identity data in both systems and agree on what "same user" means. Email alone isn't enough if either system has had years of organic data entry.

Understand the guardrails (or lack of them) in place. I didn't fully understand how Contact and Community User records could affect each other after initial creation. Identifying the gaps earlier would have given us time to start cleanup before it became a blocker.

Clarify the outage story up front. We landed in a fine place, but the conversation about "what happens if Salesforce goes down" should happen before architecture review, not after. Stakeholders need to consciously accept that tradeoff.

The actual lesson

SSO looks like a flip switch from the outside. From the inside, it's a question about where truth lives across every system you're connecting — and whether the data in those systems is actually trustworthy enough to build on.

Once we answered that question — Salesforce is the source of truth, everything else trusts it — the architecture followed naturally. OIDC handled the protocol. Just-in-time provisioning handled new users. Deprovisioning became a side effect of Salesforce offboarding. The complexity didn't disappear; it just moved into a place where it was visible and manageable.

That's the difference between bolting on SSO and actually solving the identity problem.

Comments welcome!