In this article
May 20, 2025
May 20, 2025

Why implementing SAML from scratch is a terrible idea

SAML might look simple, but under the hood, it’s a legacy minefield of XML signatures, IdP quirks, and security pitfalls. Here’s why building it yourself is a guaranteed regret.

At first glance, SAML seems like just another XML-based protocol you could spin up in a weekend. Just another XML-based protocol. There are a few blog posts, some open-source libraries, and the initial spec isn’t that long. You might think, “How hard could it be to support one Identity Provider for SSO?”

But here’s the truth: building SAML from scratch is a decision you’ll almost certainly regret. What starts as a weekend side project quickly morphs into an ongoing source of pain, bugs, maintenance debt, and worst of all, customer friction.

In this article, we will see why.

What is SAML?

SAML (Security Assertion Markup Language) is an open standard that enables identity federation—specifically, Single Sign-On (SSO)—between an identity provider (IdP) (like Okta, Azure AD, or ADFS) and a service provider (SP) (your application). At a high level, it lets a user authenticate with their identity provider and then access your app without needing to log in again.

SAML is powerful. It’s also ancient.

SAML 2.0 was standardized in 2005. That means it’s a product of its time: verbose XML, arcane schemas, and security practices that require serious expertise to implement correctly.

How SAML works

Here’s how SAML works, technically:

  1. Your app (the SP) initiates an AuthnRequest, which is often sent via an HTTP redirect or POST binding. This request is encoded (and sometimes signed) XML.
  2. The IdP validates the request and prompts the user to authenticate, typically with a username, password, MFA, etc.
  3. After authentication, the IdP generates a SAML Assertion—a signed XML document that includes claims about the user (like their email, name, and a unique identifier).
  4. The assertion is sent back to your app, typically via an HTTP POST to your SAML ACS (Assertion Consumer Service) endpoint.
  5. Then, your app validates the assertion. This involves:
    1. Verifying the digital signature against the IdP’s public certificate.
    2. Ensuring the assertion hasn’t expired.
    3. Checking conditions like audience restrictions and recipient fields.Parsing nested XML elements to extract attributes like NameID or custom claims.
  6. If everything checks out, your app creates a session and logs the user in.
  
+--------+             +-------------------+              +-------------------+
|  User  |             |  Service Provider |              | Identity Provider |
|        |             |       (SP)        |              |        (IdP)      |
+--------+             +-------------------+              +-------------------+
    |                          |                                     |
    |     Accesses App         |                                     |
    |------------------------->|                                     |
    |                          |                                     |
    |                          |  Redirects to IdP with SAML Request |
    |                          |------------------------------------>|
    |                          |                                     |
    |                          |        Authenticates User           |
    |                          |<------------------------------------|
    |                          |                                     |
    |                          | Sends SAML Assertion (POST to ACS)  |
    |                          |<------------------------------------|
    |                          |                                     |
    | Validated & Logged In    |                                     |
    |<-------------------------|                                     |
  

A SAML integration also involves exchanging metadata—XML files that describe endpoints, certificates, and supported bindings for both SP and IdP.

Keeping this metadata current is non-trivial, especially as certs expire and endpoints change.

And all of this is wrapped in namespaces, schemas, and binding profiles that are deeply legacy and notoriously difficult to implement from scratch.

The illusion of simplicity

SAML gives off strong “we can build this in a sprint” energy.

At first glance, it looks approachable:

  • “We only need to support Okta for one customer.”
  • “There’s a library for it in our stack—Node, Python, Go—should be plug-and-play.”
  • “It’s just exchanging XML between two systems. How bad could it be?”

The first proof of concept often does seem to work—until you try to move beyond a sandbox environment.

Then the edge cases start surfacing. Certificates need to be rotated. ADFS doesn’t behave like Okta. Your metadata expires silently. An assertion fails validation because of an invisible whitespace difference in a NameID.

What looked like a quick win quickly becomes a maintenance nightmare.

The problem isn’t the basic handshake—it’s everything else:

  • The unpredictable behavior of real-world Identity Providers.
  • The brittle and verbose XML signature format.
  • The opaque failure modes.
  • The subtle, security-critical validations that are easy to overlook and hard to test.

SAML only looks simple because its most painful complexity is deferred, right until you're on the hook for making it production-ready and enterprise-reliable.

The real challenges of SAML

Cryptographic complexity

SAML relies on XML Digital Signatures and often XML Encryption—two technologies that are notoriously difficult to implement and validate securely.

Unlike the compact and predictable format of something like a JWT, XML signatures operate on deeply nested documents where even small formatting differences (like whitespace or attribute order) can break verification. This is due to XML canonicalization, a process you’ll need to understand and implement correctly just to get basic signature checks to pass.

Then there’s the challenge of multiple signature locations. A SAML response can contain a signature on the response itself, the assertion, or both—each with slightly different implications. Validating the wrong one, or trusting an unsigned assertion, opens the door to serious vulnerabilities like signature wrapping attacks.

To make matters worse, you also need to manage certificate verification:

  • Validating the assertion’s signature against the correct public key.
  • Ensuring the certificate hasn’t expired.
  • Matching what’s in the assertion to the IdP metadata.

Add in encryption support, and suddenly your app needs to manage private keys, decrypt responses, and then verify signatures after decryption without introducing any security gaps.

In short, the cryptographic layer of SAML is filled with nuanced edge cases, fragile tooling, and serious security consequences if you get it wrong.

Every identity provider is different

SAML is a standard, but in the wild, it’s more like a loosely followed suggestion. Every Identity Provider (IdP) implements it a little differently, and those differences quickly add up.

  • Metadata isn’t standardized: Some IdPs offer clean, downloadable metadata files. Others require manual configuration via a clunky admin UI, omit key fields, use unexpected formats, or provide certificates that don’t match their actual SAML responses. You’ll end up writing one-off parsers, validators, and fallback logic just to get integrations working.
  • Signature and encryption behaviors vary: Okta might sign the assertion, while ADFS might only sign the response. Azure AD could encrypt everything by default, forcing you to support multiple verification paths and decryption workflows just to handle the basics.
  • Attribute & identifier chaos: One IdP sends email as NameID, another uses an opaque GUID, and a third buries user info in a custom XML attribute. There’s no reliable standard across providers, so you’ll need per-IdP logic just to extract the user ID.
  • No predictable testing: Testing isn’t as simple as flipping a switch. You’ll need test tenants, invited users, and multi-step config flows—many of which are undocumented or hidden behind enterprise-only features.

Supporting SAML for one IdP might seem fine. Supporting it for five means maintaining a patchwork of provider-specific hacks and workarounds—forever.

Developer tooling is a mess

Debugging SAML is...not fun:

  • Assertions are base64-encoded blobs in POST bodies.
  • Errors are often invisible to users (or just show up as generic HTTP 500s).
  • Tools for inspecting and testing SAML flows are limited and hard to use.

Even logging a failed login attempt can take hours to decipher.

There’s no standard CLI, no sandbox environments, and little support for local dev flows. Setting up a test IdP usually involves public URLs, manual metadata exchanges, and trial-and-error in clunky admin portals.

Even open-source libraries offer limited relief—they often skip critical validation steps unless configured just right.

SAML has all the complexity of a security protocol, and none of the modern tooling to help you work with it.

Maintenance never ends

Getting your SAML integration working once is just the start. The real cost shows up later—in the form of brittle, ongoing upkeep.

IdPs rotate certificates, change metadata, and sometimes break configs entirely. If your app doesn’t support automated metadata refresh or handle multiple certs, login flows will suddenly fail—often during critical business hours.

You’ll also field support requests from enterprise IT teams who misconfigure attributes, toggle encryption settings, or upload invalid XML. Meanwhile, SAML errors are notoriously opaque, and debugging typically requires full reproduction across dev, staging, and production environments.

You don’t just build SAML—you inherit it. Along with its indefinite, unpredictable maintenance burden.

Security pitfalls

SAML has been at the center of several high-profile vulnerabilities—not because it’s inherently broken, but because implementing it correctly is extremely difficult.

  • XML signature wrapping: Attackers can exploit weaknesses in how your code interprets XML to trick it into verifying a legitimate signature, but using a forged assertion. This is one of the most dangerous and well-documented risks.
  • Misconfigured audience restrictions: SAML assertions include an Audience value that should match your app’s expected recipient. If you forget to validate this, any SAML assertion—even one meant for another service—might be accepted.
  • Insecure XML parsers: Without strict configurations, XML parsers can be vulnerable to XXE (XML External Entity) attacks, allowing attackers to read files from your server or make internal network requests.
  • Replay attacks: If you don’t enforce assertion expiration or replay protection (using unique IDs and timestamps), an attacker could reuse a valid assertion to gain access again.
  • Inconsistent validation: Open-source libraries often leave signature, certificate, and format validation up to the implementer. This flexibility leads to insecure defaults and subtle bugs that can be exploited.

In short: SAML is a minefield, and unless you're a seasoned expert in identity protocols and application security, you’re playing with fire.

Common implementation mistakes

Even when developers approach SAML with good intentions, the protocol’s complexity and ambiguity lead to predictable mistakes—ones that are difficult to diagnose and even harder to recover from in production. Here are some of the most common pitfalls seen in homegrown implementations:

  1. Trusting the wrong signature: Many implementers validate the signature on the SAML Response but not on the Assertion itself—or vice versa. Some identity providers sign only the response, others only the assertion, and a few do both. Failing to verify the right signature, or mistakenly trusting unsigned content, opens the door to signature wrapping attacks and assertion forgery.
  2. Skipping audience validation: Every SAML assertion includes an Audience field, which should match your application’s unique identifier (usually a URL). Many devs overlook this check, especially when debugging broken logins. This allows malicious actors to reuse valid assertions issued for one app in another—a serious security breach.
  3. Improper XML parsing: Standard XML parsers are often misconfigured, leading to vulnerabilities like XXE (XML External Entity) injection. Developers frequently forget to disable external entity resolution, leaving their app open to file disclosure, SSRF, and other dangerous exploits.
  4. Assuming metadata is static: Metadata is treated like a one-time setup artifact—but in reality, it changes. Identity providers rotate certificates, change endpoints, and occasionally update binding methods. If your app doesn’t fetch and re-validate metadata regularly, you’ll hit unplanned downtime when a cert expires or a config changes silently.
  5. Overlooking assertion replay protection: Assertions should be single-use. Failing to store assertion IDs and enforce replay protection means an attacker can reuse a valid assertion (e.g., intercepted in a dev or staging environment) to log in again—potentially across environments or accounts.
  6. Hardcoding IdP-specific behaviors: Initial implementations often work by accident—thanks to testing against a single provider like Okta. But those shortcuts become tech debt when you add support for Azure AD, ADFS, or Ping. Things like signing behavior, encryption defaults, and attribute formats vary wildly. Hardcoding logic to a single provider’s quirks guarantees future pain.
  7. Not surfacing useful errors: When SAML authentication fails, many apps log a generic HTTP 500 or “Login failed.” That’s a missed opportunity. Without proper logging (including base64-decoded assertion dumps, certificate fingerprints, and parsing errors), you’ll be flying blind when issues arise—and they will.
  8. Using insecure defaults from libraries: Most open-source SAML libraries are low-level tools, not batteries-included solutions. They often default to insecure settings—like skipping audience validation, not enforcing expiration windows, or accepting unsigned assertions—unless you explicitly configure them. Developers assume “it just works” means “it’s secure.” It doesn’t.

The right way to do SAML

SAML isn’t a feature. It’s infrastructure. And it demands to be treated like such.

The only right way to do SAML is not to build it yourself. Offload it.

SAML is a textbook example of something critical but undifferentiated:

  • It’s complicated.
  • It’s fragile.
  • It delivers no competitive advantage.

At WorkOS, we’ve abstracted away the chaos. Our SSO product handles SAML and other enterprise auth protocols through a single, unified API. We normalize differences across identity providers, handle metadata and cert rotations, and harden every edge case.

You can integrate enterprise-grade SSO in hours—not months—and never touch an XML signature again.

Building SAML from scratch is like hand-crafting a compiler just to print “Hello, World.” You can do it—but you really, really shouldn’t.

Let WorkOS handle SAML so you can focus on what matters: your product.

Sign up for WorkOS SSO today.

This site uses cookies to improve your experience. Please accept the use of cookies on this site. You can review our cookie policy here and our privacy policy here. If you choose to refuse, functionality of this site will be limited.