SAML 2.0 — The Enterprise SSO Standard
Thilan Dissanayaka Identity & Access Management May 22, 2020

SAML 2.0 — The Enterprise SSO Standard

If OIDC is the modern, JSON-based protocol that powers "Sign in with Google," then SAML is its older, XML-heavy sibling that runs the corporate world. It's been around since 2005, it's verbose, it uses XML signatures, and enterprise IT departments love it.

Every time you click an app icon in your corporate Okta or Azure AD dashboard and magically land on Salesforce or Jira without typing a password — that's SAML doing the heavy lifting behind the scenes.

In the previous article, we covered OIDC and briefly compared it with SAML. Now let's go deep into how SAML actually works — the assertions, the bindings, the flows, and most importantly, the security attacks that make SAML a fascinating target for security researchers.

What is SAML and Why Does It Still Matter?

SAML stands for Security Assertion Markup Language. It's an XML-based open standard for exchanging authentication and authorization data between parties — specifically between an Identity Provider (IdP) and a Service Provider (SP).

SAML 2.0 was standardized by OASIS in 2005. That's ancient by internet standards. So why does it still matter?

  • Enterprise entrenchment — Thousands of enterprise applications support SAML. Salesforce, AWS Console, Workday, ServiceNow — they all speak SAML. Migrating to OIDC would require coordinated changes across hundreds of integrations.
  • Compliance and auditing — Many compliance frameworks reference SAML-based federation. Changing the protocol means re-certifying.
  • Vendor support — Enterprise IdPs like ADFS (Active Directory Federation Services), Okta, and Ping Identity have deep SAML support with years of battle-tested configurations.

OIDC is gaining ground, but SAML isn't going anywhere soon.

SAML Terminology

Term Meaning
Identity Provider (IdP) The system that authenticates users and issues assertions (Okta, ADFS, WSO2 IS)
Service Provider (SP) The application that consumes assertions and grants access (Salesforce, Jira, AWS Console)
Principal The user being authenticated
Assertion An XML document containing statements about the user
Binding How SAML messages are transported (HTTP POST, HTTP Redirect, SOAP)
Metadata XML configuration documents that establish trust between IdP and SP
AuthnRequest The SP's request to the IdP for authentication
SAML Response The IdP's response containing the assertion
RelayState A URL that tells the SP where to redirect the user after SSO
ACS (Assertion Consumer Service) The SP endpoint that receives SAML responses

SAML Assertions — The Core Artifact

A SAML assertion is an XML document that makes statements about a user. There are three types of statements:

  1. Authentication Statement — "This user was authenticated at this time using this method"
  2. Attribute Statement — "This user has these attributes (name, email, roles)"
  3. Authorization Decision Statement — "This user is authorized to do X on resource Y" (rarely used in practice)

Here's a simplified SAML assertion:

<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
    ID="_assertion123"
    Version="2.0"
    IssueInstant="2024-05-20T10:30:00Z">

    <saml:Issuer>https://idp.company.com</saml:Issuer>

    <ds:Signature>
        <!-- XML Digital Signature over this assertion -->
    </ds:Signature>

    <saml:Subject>
        <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">
            [email protected]
        </saml:NameID>
        <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
            <saml:SubjectConfirmationData
                NotOnOrAfter="2024-05-20T10:35:00Z"
                Recipient="https://app.example.com/saml/acs"
                InResponseTo="_request456" />
        </saml:SubjectConfirmation>
    </saml:Subject>

    <saml:Conditions NotBefore="2024-05-20T10:29:00Z"
                     NotOnOrAfter="2024-05-20T10:35:00Z">
        <saml:AudienceRestriction>
            <saml:Audience>https://app.example.com</saml:Audience>
        </saml:AudienceRestriction>
    </saml:Conditions>

    <saml:AuthnStatement AuthnInstant="2024-05-20T10:30:00Z"
                         SessionIndex="_session789">
        <saml:AuthnContext>
            <saml:AuthnContextClassRef>
                urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
            </saml:AuthnContextClassRef>
        </saml:AuthnContext>
    </saml:AuthnStatement>

    <saml:AttributeStatement>
        <saml:Attribute Name="email">
            <saml:AttributeValue>[email protected]</saml:AttributeValue>
        </saml:Attribute>
        <saml:Attribute Name="role">
            <saml:AttributeValue>admin</saml:AttributeValue>
        </saml:Attribute>
        <saml:Attribute Name="department">
            <saml:AttributeValue>Engineering</saml:AttributeValue>
        </saml:Attribute>
    </saml:AttributeStatement>

</saml:Assertion>

Let's break down the key elements:

Element Purpose
Issuer The IdP that created this assertion
Signature XML digital signature — proves the assertion wasn't tampered with
Subject/NameID The user's identity (usually email or an opaque ID)
SubjectConfirmation How the SP should confirm the subject (bearer = just present the token)
InResponseTo Links the assertion to a specific AuthnRequest (prevents replay)
Conditions Time window and audience restrictions for the assertion
AudienceRestriction Which SP this assertion is intended for
AuthnStatement When and how the user authenticated
AttributeStatement User attributes passed to the SP

SP-Initiated SSO Flow

This is the most common SAML flow. The user starts at the Service Provider and gets redirected to the IdP for authentication.

    Browser              Service Provider (SP)       Identity Provider (IdP)
       |                        |                            |
  1.   | --- Visit app -------> |                            |
       |                        |                            |
  2.   |                        | (User not authenticated)   |
       |                        | Generate AuthnRequest      |
       |                        |                            |
  3.   | <-- Redirect to IdP -- |                            |
       |    (with AuthnRequest) |                            |
       |                        |                            |
  4.   | --- AuthnRequest ------------------------------>    |
       |                        |                            |
  5.   | <--- Login page --------------------------------    |
       |                        |                            |
  6.   | --- Credentials ----------------------------------> |
       |                        |                            |
  7.   |                        |      IdP authenticates     |
       |                        |      Creates SAML Response |
       |                        |      Signs assertion       |
       |                        |                            |
  8.   | <-- Auto-submit form (HTTP POST) with SAMLResponse  |
       |                        |                            |
  9.   | --- POST SAMLResponse -> |                          |
       |                        |                            |
  10.  |                        | Validate signature         |
       |                        | Check conditions           |
       |                        | Extract user attributes    |
       |                        | Create session             |
       |                        |                            |
  11.  | <-- Access granted --- |                            |

Step 3 — The AuthnRequest:

<samlp:AuthnRequest
    xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
    ID="_request456"
    Version="2.0"
    IssueInstant="2024-05-20T10:29:50Z"
    Destination="https://idp.company.com/saml/sso"
    AssertionConsumerServiceURL="https://app.example.com/saml/acs"
    ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST">

    <saml:Issuer>https://app.example.com</saml:Issuer>

    <samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" />

</samlp:AuthnRequest>

This AuthnRequest says: "I'm app.example.com. Please authenticate the user and send the response to my ACS URL using HTTP POST."

Step 8-9 — The SAML Response (via HTTP POST):

The IdP creates an HTML page with a hidden form that auto-submits:

<html>
<body onload="document.forms[0].submit()">
    <form method="POST" action="https://app.example.com/saml/acs">
        <input type="hidden" name="SAMLResponse" value="PHNhbWxwOl..." />
        <input type="hidden" name="RelayState" value="https://app.example.com/dashboard" />
    </form>
</body>
</html>

The SAMLResponse value is a base64-encoded XML document containing the signed assertion.

IdP-Initiated SSO Flow

In this flow, the user starts at the IdP — typically a corporate dashboard (Okta, Azure AD portal) — and clicks an app icon.

The IdP sends a SAML Response directly to the SP without a preceding AuthnRequest. This is simpler but less secure because:

  • There's no InResponseTo value (no request to bind the response to)
  • This makes it harder to prevent replay attacks
  • The SP must trust the assertion based solely on its signature and conditions

Despite the security trade-offs, IdP-initiated SSO is widely used in corporate environments because it provides a convenient "app launcher" experience.

SAML Bindings

Bindings define how SAML messages are transported over HTTP.

HTTP Redirect Binding

The SAML message is deflated (compressed), base64-encoded, and sent as a URL query parameter:

GET https://idp.company.com/saml/sso?
    SAMLRequest=fZJNT8MwEITvSPwH...
    &RelayState=https://app.example.com/dashboard
    &SigAlg=http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
    &Signature=dGhpcyBpcyBh...

Used for AuthnRequests (small messages). Not suitable for SAML Responses because they're too large for URLs.

HTTP POST Binding

The SAML message is base64-encoded and sent in a form POST body:

POST https://app.example.com/saml/acs
Content-Type: application/x-www-form-urlencoded

SAMLResponse=PHNhbWxwOlJlc3BvbnNlIHhtbG5z...&RelayState=https://app.example.com/dashboard

Used for SAML Responses (large messages with assertions and signatures). The auto-submitting HTML form pattern makes this seamless for the user.

XML Signatures in SAML

The signature is what makes SAML assertions trustworthy. Without it, anyone could craft an assertion claiming to be any user.

SAML uses XML Digital Signatures (XMLDSig), which can be placed at two levels:

  1. Signed Assertion — The <Assertion> element itself is signed. This is the most important — it proves the assertion content hasn't been tampered with.
  2. Signed Response — The entire <Response> wrapper is signed. Provides integrity for the response metadata.

Best practice: sign the assertion (always), and optionally sign the response as well.

The signature structure looks like this:

<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
    <ds:SignedInfo>
        <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
        <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
        <ds:Reference URI="#_assertion123">
            <ds:Transforms>
                <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
                <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
            <ds:DigestValue>base64-encoded-digest</ds:DigestValue>
        </ds:Reference>
    </ds:SignedInfo>
    <ds:SignatureValue>base64-encoded-signature</ds:SignatureValue>
    <ds:KeyInfo>
        <ds:X509Data>
            <ds:X509Certificate>base64-encoded-certificate</ds:X509Certificate>
        </ds:X509Data>
    </ds:KeyInfo>
</ds:Signature>

The SP validates the signature using the IdP's public certificate (obtained through metadata exchange during setup).

SAML Metadata

Trust between IdP and SP is established through metadata exchange. Both sides publish XML metadata documents describing their endpoints, certificates, and capabilities.

IdP Metadata (abbreviated):

<EntityDescriptor entityID="https://idp.company.com"
    xmlns="urn:oasis:names:tc:SAML:2.0:metadata">

    <IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
        <KeyDescriptor use="signing">
            <ds:KeyInfo>
                <ds:X509Data>
                    <ds:X509Certificate>MIICpDCCAYwC...</ds:X509Certificate>
                </ds:X509Data>
            </ds:KeyInfo>
        </KeyDescriptor>
        <SingleSignOnService
            Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
            Location="https://idp.company.com/saml/sso" />
    </IDPSSODescriptor>

</EntityDescriptor>

SP Metadata (abbreviated):

<EntityDescriptor entityID="https://app.example.com"
    xmlns="urn:oasis:names:tc:SAML:2.0:metadata">

    <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"
        AuthnRequestsSigned="true">
        <AssertionConsumerService
            Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
            Location="https://app.example.com/saml/acs"
            index="0" />
    </SPSSODescriptor>

</EntityDescriptor>

Typically, you import the IdP's metadata into your SP configuration (or vice versa), and the trust relationship is established automatically.

SAML Security — Attacks and Defenses

This is where SAML gets really interesting from a security perspective. The complexity of XML parsing and signature validation creates a large attack surface.

XML Signature Wrapping (XSW)

This is the most dangerous SAML attack. It exploits a fundamental weakness in how XML signatures work.

The XML signature signs a specific element identified by a URI reference (e.g., #_assertion123). But what if the attacker moves the signed element to a different location in the XML tree and inserts a forged element in the original location?

The signature validation passes (because the signed element still exists and hasn't been modified). But the application processes the forged element (because it's in the expected location).

Before the attack:

<Response>
    <Assertion ID="_assertion123">   <!-- This is signed and processed -->
        <Subject>[email protected]</Subject>
    </Assertion>
</Response>

After XSW attack:

<Response>
    <Assertion ID="_forged">          <!-- FORGED — this gets processed -->
        <Subject>[email protected]</Subject>
    </Assertion>
    <Assertion ID="_assertion123">    <!-- Original — signature still valid -->
        <Subject>[email protected]</Subject>
    </Assertion>
</Response>

The SP's XML parser finds the first <Assertion> element and processes it — the forged one. The signature validator finds the element with ID="_assertion123" and validates it — the original one. Both succeed, but on different elements.

Defense: After signature validation, ensure that the exact element that was signed is the one you process. Use strict XML schema validation. Libraries like xmlsec handle this correctly when configured properly.

Replay Attack

An attacker captures a valid SAML Response and replays it later to gain unauthorized access.

Defense:

  • Check NotOnOrAfter — reject assertions that have expired
  • Check InResponseTo — match the response to a specific AuthnRequest you sent
  • Track AssertionID — reject assertions you've already seen
  • Use OneTimeUse condition when available

XXE (XML External Entity)

SAML messages are XML, which means they're vulnerable to XXE if the XML parser isn't hardened:

<?xml version="1.0"?>
<!DOCTYPE foo [
    <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<samlp:Response>
    <saml:Assertion>
        <saml:Subject>
            <saml:NameID>&xxe;</saml:NameID>
        </saml:Subject>
    </saml:Assertion>
</samlp:Response>

If the SP's XML parser processes external entities, the attacker can read files from the server, perform SSRF, or cause denial of service.

Defense: Disable external entity processing in your XML parser. In Java:

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

Golden SAML

If an attacker compromises the IdP's signing certificate (the private key), they can forge SAML assertions for any user — including administrators — across every SP that trusts that IdP.

This is analogous to a Golden Ticket attack in Kerberos. The attacker doesn't need to compromise the SP or the user's credentials. They just forge an assertion and present it.

This was famously used in the SolarWinds supply chain attack (2020), where the attackers compromised the ADFS signing certificate and forged SAML assertions to access cloud resources.

Defense:

  • Protect signing keys with HSMs (Hardware Security Modules)
  • Monitor for anomalous SAML assertions (unexpected users, unusual attributes)
  • Rotate signing certificates periodically
  • Implement anomaly detection on the SP side (unexpected source IPs, unusual access patterns)

Open Redirect via RelayState

The RelayState parameter tells the SP where to redirect the user after SSO. If the SP doesn't validate this URL, an attacker can set it to an external domain:

RelayState=https://attacker.com/phishing

Defense: Validate that RelayState points to a URL within your application's domain.

SAML vs OIDC — Detailed Comparison

Aspect SAML 2.0 OIDC
Year 2005 2014
Token format XML (verbose) JSON/JWT (compact)
Token size 5-20KB ~1KB
Transport HTTP Redirect + POST HTTP Redirect + back-channel
Signature XML Digital Signatures JWS (JSON Web Signature)
Mobile support Poor (XML parsing, large tokens) Excellent
API support Limited Native (Bearer tokens)
Complexity High (XML, canonicalization, schemas) Lower
Discovery Metadata XML .well-known/openid-configuration
Enterprise adoption Dominant Growing
Use case Enterprise SSO, legacy apps Modern web, mobile, APIs

When to use SAML:

  • Integrating with enterprise applications that only support SAML
  • Your IdP and SPs already have SAML configured
  • Compliance requirements specify SAML

When to use OIDC:

  • Building new applications
  • Mobile or SPA clients
  • API-first architectures
  • You want simplicity and modern tooling

Testing Tools

Tool Purpose
SAML Tracer Browser extension that captures SAML requests/responses in real time
SAMLTool.com Online tool for encoding/decoding SAML messages, validating signatures
SAMLRaider Burp Suite extension for SAML security testing (XSW attacks, signature manipulation)
OneLogin SAML Toolkit Libraries for implementing SAML SP in various languages

Final Thoughts

SAML is complex, verbose, and XML-heavy. But it's also battle-tested, widely supported, and deeply embedded in enterprise infrastructure. If you work in enterprise security, you will encounter SAML — and understanding its internals is essential for both implementing it correctly and testing it for vulnerabilities.

The XML Signature Wrapping attack alone has affected hundreds of SAML implementations over the years. It's a beautiful example of how the gap between "signature validates" and "the right thing is signed" can lead to complete authentication bypass.

Whether you're configuring SAML in WSO2 Identity Server, testing it with Burp Suite, or planning a migration to OIDC — the fundamentals covered here will serve you well.

Stay secure!

ALSO READ
 OWASP Top 10 explained - 2021
Feb 11 Application Security

The Open Worldwide Application Security Project (OWASP) is a nonprofit foundation focused on improving the security of software. It provides free, vendor neutral tools, resources, and standards that...

Singleton Pattern explained simply
Jan 27 Software Architecture

Ever needed just one instance of a class in your application? Maybe a logger, a database connection, or a configuration manager? This is where the Singleton Pattern comes in — one of the simplest but...

Common Web Application Attacks
Feb 05 Application Security

Web applications are one of the most targeted surfaces by attackers. This is primarily because they are accessible over the internet, making them exposed and potentially vulnerable. Since these...

Exploiting a format string vulnerebility on Linux
Apr 12 Exploit development

A misused printf can leak stack contents, read arbitrary memory, and write to arbitrary addresses. Format string vulnerabilities are one of the most powerful bug classes in C and they're the key to defeating ASLR. In this post, we exploit printf from leak to shell.

Understanding the Heap Internals
Apr 12 Exploit development

So far in this series, we've exploited the **stack** buffer overflows, ROP chains, format strings. The stack is predictable: local variables go in, function returns pop them out, everything follows a...

Exploiting a  Stack Buffer Overflow  on Linux
Apr 01 Exploit development

Have you ever wondered how attackers gain control over remote servers? How do they just run some exploit and compromise a computer? If we dive into the actual context, there is no magic happening....