SASE, SAML & Modern Identity: Part 2 — SAML 2.0
Part 2 of 3. Part 1 covered SASE architecture. This one is SAML 2.0 — how federated identity actually works, what the assertion contains, how the flows run, and where it breaks. The vulnerability section is worth reading even if you're not a security engineer. These attacks show up in the wild regularly.
What SAML Is
SAML (Security Assertion Markup Language) is a federated identity protocol. It lets one system — the Identity Provider — vouch for a user's identity to another system — the Service Provider — without the user ever handing their credentials to the SP directly.
Two actors:
IDP (Identity Provider): The authority that knows who the user is. Okta, Microsoft Entra, Google Workspace. The IDP authenticates the user and issues signed assertions.
SP (Service Provider): The application or resource the user wants to access. The HR portal, the finance dashboard, Salesforce. The SP trusts the IDP's word and grants access based on the assertion it receives.
The analogy from the notes holds well: the IDP is your government issuing you a passport. The SP is border control at another country. The passport — the SAML assertion — is the signed document that proves who you are. The SP doesn't need to know your government's internal records. It just needs to trust that the issuing authority is legitimate and that the document hasn't been forged.
The SAML Assertion
The assertion is the core artifact of SAML. It's an XML document the IDP generates, signs, and sends to the SP. The SP reads it, validates it, and uses it to make an access decision. Every field in the assertion exists for a specific security reason.
Issuer
A globally unique identifier for the IDP. The SP uses this to confirm the assertion came from the authority it's configured to trust, not some other IDP trying to impersonate it.
NameID
The user's identifier within the assertion. Typically an email address or an employee ID. It must be formatted exactly as the SP expects. If the IDP sends an email address and the SP expects a numeric employee ID, the assertion will fail validation even if everything else is correct.
Subject Confirmation Data
Defines the exact, unalterable conditions under which the assertion is considered valid. If any single condition in this block fails, the SP must immediately discard the assertion.
InResponseTo: A reference to the unique request ID that the SP generated when it initiated the authentication flow. The SP checks that this value matches what it sent. An assertion that arrives without a matching request ID — or with someone else's request ID — gets rejected.
Recipient: The specific URL where the assertion must be delivered. This is the SP's ACS (Assertion Consumer Service) endpoint. If the assertion is delivered to any other URL, the parsing engine must reject it. This prevents an intercepted assertion from being replayed against a different SP.
NotOnOrAfter: A hard expiration timestamp. After this moment, the assertion is no longer valid and must be rejected regardless of everything else. These windows are typically very short — often 60 seconds — because the assertion only needs to be valid for the brief time it's in transit.
Conditions Block
Contains a broader set of validity conditions that wrap the assertion:
NotBefore: The assertion is not valid before this timestamp. Prevents clock skew from being exploited by someone trying to use a pre-issued assertion before its intended validity window.
NotOnOrAfter: The same expiration logic as above, applied at the conditions level.
Audience Restriction: Specifies which SP the assertion is intended for, identified by its entity ID. If an attacker intercepts a valid assertion meant for your HR application and tries to replay it against your financial database, the database checks the audience restriction, sees its own entity ID isn't listed, and discards the document immediately. This makes stolen assertions useless against unintended targets.
AuthnStatement
Describes the authentication event that already occurred at the IDP. The SP doesn't re-authenticate the user — it just reads what the IDP did.
AuthnContextClassRef: Tells the SP how the user authenticated. Was it a password? A hardware token? MFA with a push notification? This lets the SP make context-sensitive access decisions — some applications might require a higher assurance level than a simple password.
AuthnInstant: The exact UTC timestamp of when authentication took place at the IDP.
Digital Signature
The IDP takes the XML document, runs it through a hashing algorithm, and encrypts the resulting hash using its private key. This encrypted hash is the signature. The SP decrypts the signature using the IDP's public key, independently computes the hash of the received document, and compares the two. If they match, the document is confirmed to be from the IDP and unmodified in transit.
Metadata Exchange
Before any of the above works, the IDP and SP need to know about each other. This happens offline — IT administrators on both sides exchange a metadata document before the integration goes live. Everything the two parties need to establish trust is in this document.
The metadata contains:
Entity identifiers: Globally unique names for both the IDP and the SP.
ACS URLs: The exact endpoint URLs where the IDP should deliver assertions. The IDP reads this so it knows where to POST the response.
Supported bindings: The specific HTTP mechanisms both parties support for transporting SAML messages (more on bindings below).
Public keys and certificates: The IDP's certificate and public key, which the SP imports into its trust store. This is the key material the SP uses to verify assertion signatures. If the SP's metadata includes its own public key, the IDP can use it to encrypt sensitive attributes inside the assertion — things like SSNs or salary data — so only the SP's private key can decrypt them.
An important operational note on certificates: When these certificates expire, signature validation fails and SSO can break entirely. Some platforms support metadata refresh or staged rollover with multiple certificates, but plenty of enterprise SAML integrations still fail when the IDP signing certificate changes and the SP has not imported the new metadata. Treat certificate rotation as a coordinated operation, not an afterthought.
Why Bindings Exist
The SP and IDP servers don't communicate directly with each other during a SAML flow. They hand messages to the user's browser and instruct it to deliver them. Artifact binding is the exception: the browser carries only a reference, and the SP uses a direct SOAP back-channel to retrieve the real SAML message.When SAML was designed, enterprise firewalls were rigid, and creating direct connections between every application and the IDP would require a large number of open ports and firewall policies. Using the browser as the courier meant everything ran over port 443 with existing 'rigid firewall' infrastructure. This is why SAML is described as browser-mediated federation.
The Four Main Bindings
1. HTTP Redirect: The SAML message is compressed, base64-encoded, and appended to a URL as a query parameter. The browser carries it as a redirect. Used for short messages — specifically the initial authentication request from the SP to the IDP, which is small enough to fit in a URL.
2. HTTP POST: The SAML message is placed in the body of an HTTP POST request. The IDP generates an HTML page containing a hidden form. The form holds the base64-encoded SAML XML inside it. The IDP returns this HTML page to the user's browser.
The base64 encoding exists because raw XML contains special characters that a destination web server's parser might interpret as HTML markup, breaking the transmission. Base64 converts the binary representation of the XML into a 64-character alphabet of safe symbols — letters, numbers, and a small set of punctuation — that travels cleanly.
The form is hidden so the user never sees the XML payload. The page also contains JavaScript that automatically submits the form to the SP's ACS URL. The user's screen might flicker for a fraction of a second and then the page loads. That's the entire exchange from the user's perspective.
HTTP POST is used for the SAML response carrying the assertion — the most sensitive part of the flow.
3. HTTP Artifact: For higher security environments where sending the full assertion through the browser is undesirable. Instead of the actual XML, the sender generates a short UUID-like token called an artifact and sends only that through the browser via a redirect. When the receiver gets the artifact, it opens a direct server-to-server back-channel connection to the sender — bypassing the browser entirely — and uses that channel to exchange the artifact for the full XML document. This back-channel uses the SOAP binding.
4. SOAP Binding: A direct synchronous XML-based communication channel between two servers. The SP's server opens a direct socket connection to the IDP's server over port 443 and sends an HTTP POST request whose body contains a SOAP envelope wrapping the SAML message. The user's browser is not involved at any point. Used as the back-channel mechanism in the Artifact binding and in scenarios where assertion content must never pass through the client's machine.
SP-Initiated SSO
The most common flow. The user starts from the SP side.
- A user navigates to the HR application's URL. The SP receives the GET request, checks for a local session cookie, finds none, and refuses access to the dashboard.
- The SP crafts a small XML authentication request — essentially asking the IDP to identify this user — and provides the browser with an HTTP 302 redirect pointing to the IDP's SSO URL. The authentication request is embedded in the URL's query string using the HTTP Redirect binding. The SP also includes a relay state parameter in this redirect: an opaque state value that records, or points back to, where the user was trying to go, so that after authentication completes they land on the specific HR page they had open rather than the generic home page.
- The browser follows the redirect and arrives at the IDP. The IDP unpacks the authentication request, identifies the SP, and presents the user with an authentication challenge — password, MFA, hardware token, whatever the policy requires. If the user passes, the IDP crafts the SAML response: embeds the assertion, sets timestamps, adds audience restrictions, and signs the document with its private key.
- The IDP uses the HTTP POST binding to send the signed SAML response, along with the relay state, to the SP's ACS URL. It does this by generating that hidden HTML form and returning it to the user's browser, which auto-submits it.
- The SP receives the POST, decodes the base64, extracts the XML, and validates the IDP's signature using the IDP's public key from its local metadata store. It verifies the InResponseTo value matches the request ID it generated in step 2. It checks that the NotOnOrAfter timestamp hasn't expired. It confirms the audience restriction matches its own entity ID.
- All checks pass. The SP creates a local session cookie for the browser, reads the relay state, and serves the user the specific HR page they originally requested.
IDP-Initiated SSO
The user starts from the IDP's portal rather than navigating to an application directly.
- The user logs into the IDP's application portal and sees a grid of application icons. They click the HR app.
- Since the user is already authenticated, the IDP generates an assertion and uses the HTTP POST binding to send it directly to the SP's ACS URL through the browser. No initial request was made by the SP.
The SP receives an assertion that has no corresponding InResponseTo value — nothing to match against, because the SP never generated a request. This is called an unsolicited response.
SP-initiated flows are preferred. When the SP generates the authentication request and creates the request ID, it knows exactly what it asked for and can validate that the response matches. In an IDP-initiated flow, the SP has no record of asking for anything. It just receives an assertion and has to decide whether to trust it.
If the SP accepts unsolicited responses, it becomes more vulnerable to CSRF attacks. An attacker logs in with their own valid credentials and captures their own SAML assertion. They then trick a victim's browser into submitting that assertion to the SP — for instance, by embedding a hidden form in a malicious webpage. The victim is logged into the SP as the attacker. Any actions the victim takes — entering personal data, uploading files — are attributed to the attacker's account. SP-initiated flows prevent this because the SP validates the InResponseTo value against a request it knows it generated, rejecting anything that doesn't match.
Misconfigurations and Attacks
How XML Signatures Actually Work
Before getting into the attacks, two things about XML signatures that are not intuitive:
XML signatures do not sign the entire document by default. They use a <Reference URI="#id"> element to sign a specific, named element within the document — usually the assertion. The rest of the document is unsigned.
Before the document is hashed, it goes through XML canonicalization: a normalization process that converts the XML into a standardized, consistent representation. Whitespace, attribute ordering, namespace declarations — all of these can differ across parsers while representing logically identical XML. Canonicalization ensures that logically equivalent documents produce the same hash, so that minor serialization differences don't cause signature validation to fail.
SAML response structure is also looser than it might appear. The <Response> element is a wrapper. Inside it there can be multiple <Assertion> elements, <Extensions> blocks, and other metadata. XML parsers classify content based on tag names, not position. If you put an assertion inside an extensions block, a parser looking for assertions will still find it there. This is the opening that XSW exploits.
XML Signature Wrapping (XSW)
This attack targets implementations where two separate software components handle signature verification and data extraction — and those two components view the XML document differently.
Ruby-saml, a widely used open-source library, historically suffered from this due to using two parsers: Nokogiri for signature verification and canonicalization, and REXML for data extraction. They process the XML document object model differently.
Nokogiri (signature validation):
Uses the XPath query //Signature — the // means "search the entire document and return the first match." It finds the signature element, follows its Reference URI to the element it covers (the real assertion, somewhere in the document), validates the cryptographic math, and reports: signature is valid.
REXML (data extraction):
Parses the XML top-down, reading the document as a sequential tree. It finds the first <Assertion> element it encounters and extracts the identity from it. It does not check whether this assertion is the one that was signed.
The attack:
The attacker has a legitimately signed SAML assertion obtained from their own low-privilege account. They inject a second, unsigned, malicious assertion into the document, setting the NameID in this fake assertion to an admin account. They place this fake assertion high in the document — inside an <Extensions> block, before the real signed assertion.
Placing it in extensions serves a purpose: some SP configurations enforce that only one assertion element is allowed per response. Putting the fake assertion inside extensions rather than directly alongside the real one can bypass that check, since the parser classifies it by tag contents, not by where it sits in the document tree.
Now the document gets processed:
Nokogiri runs its signature check. It uses the lazy //Signature query, finds the real signature at the bottom of the document, validates the math against the legitimately signed assertion, and declares the payload valid.
REXML then runs to extract the username. It reads top-down, hits the fake injected assertion first, and extracts the admin identity.
The signature check passed. The extracted identity is admin. The user is logged in with elevated privileges.
Defense:
Validate the XML schema against a locally trusted schema definition before processing. More importantly, ensure that the component validating the signature and the component extracting user data are operating on the exact same node in memory — not two independent traversals of the same document.
Replay Attacks
An attacker intercepts a SAML response in transit and captures the base64-encoded assertion from the HTTP POST body. They attempt to submit that assertion to the SP's ACS URL themselves, either immediately or later.
The primary defense is strict enforcement of the NotOnOrAfter timestamp. The assertion's validity window is typically around 60 seconds — just long enough to survive the transit from IDP to SP under normal conditions.
That 60-second window creates an edge case. What if the attacker replays the assertion within the valid window? The SP must maintain a short-term state cache of every InResponseTo ID it has successfully processed. If a second assertion arrives with an ID already in that cache — even if the timestamps are still valid — the SP discards it. Each assertion ID is single-use.
Relay State Parameter Manipulation
The relay state travels through the user's browser. An attacker positioned to intercept and modify browser traffic can tamper with the relay state value while it's in transit, replacing the legitimate return URL with a URL pointing to a malicious site.
The authentication succeeds. The SAML assertion is valid. But the manipulated relay state redirects the now-authenticated user to an attacker-controlled phishing page. The user just successfully logged in and is likely to trust whatever they're looking at. They might enter sensitive data, upload files, or take other actions the attacker wanted.
Defense: The SP must validate the relay state URL against a preconfigured allowlist of trusted return destinations before executing any redirect. Never trust input arriving from the browser without checking it against your own internal configuration.
XML External Entity (XXE) / SSRF
XML has a feature that allows document authors to define entities — essentially variables. External entities can be defined to reference outside resources, instructing the parser to fetch data from an external location to populate the variable's value.
If a SAML assertion contains a reference to an external entity pointing at a local file path on the server — for example, /etc/passwd on a Linux system — a vulnerable parser will follow the instruction, read that file, and embed its contents in the XML processing output. The attacker receives the server's password file in an error response or the assertion processing output.
More usefully for an attacker, the external entity can point to an internal network address that isn't publicly accessible — like the AWS instance metadata endpoint at 169.254.169.254. That endpoint can return API keys, IAM role credentials, and other information that an external attacker has no business accessing. This is a classic SSRF (Server-Side Request Forgery) attack vector, enabled by XML's external entity feature.
The fix is to configure XML parsers to disable external entity resolution entirely. Most modern parsers have this option; it's often off by default for compatibility reasons and needs to be explicitly set.
Troubleshooting SAML
When SAML breaks, the assertion is almost always reachable through browser developer tools.
Open DevTools before running the login flow. Go to the Network tab and check the Preserve Log box — without this, redirects will clear the request history and you'll lose the relevant entries.
Run the login flow. Filter the network requests for the POST request going to the SP's ACS URL. Inside the body of that request you'll find a parameter called SAMLResponse. Copy the base64-encoded value, paste it into a trusted base64 decoder, and read the raw XML. You can see directly whether the NameID format is wrong, the audience restriction doesn't match, the timestamps have already expired, or the InResponseTo ID is malformed.
SAML Tracer is a browser extension that automates this. It intercepts SAML traffic in the browser, decodes the base64 automatically, and presents the XML in a readable format. Think of it as Wireshark for SAML flows. Worth installing if you work with SSO integrations regularly.
SCIM vs SAML
SAML handles authentication. It does not handle the lifecycle of user accounts.
Consider the termination scenario: an employee is fired two hours into their eight-hour workday. Their account is disabled in the IDP. From SAML's perspective, future login attempts will fail because the IDP won't issue new assertions for a disabled account. But existing sessions are unaffected. The application created a local session when the user logged in this morning, and that session is still active. The employee can continue working in every application they opened before the account was disabled, until each session's own timeout expires — potentially for hours.
There's also the provisioning gap. SAML uses JIT (Just-In-Time) provisioning: it doesn't create a user's account in the application when they're assigned to it in the IDP. It waits until the user first attempts to log in, then uses the assertion to create the account on demand. New employees can't be set up in an application in advance of their first login.
SCIM (System for Cross-domain Identity Management) helps solve both problems. It's a separate protocol that runs continuously in the background, using REST APIs and JSON payloads to synchronize user lifecycles between the IDP and every connected application.
When a new hire is added to Active Directory, SCIM can immediately call the application's API and provision the account before the employee ever opens a browser. When that employee is terminated, the IDP sends a SCIM DELETE or PATCH request to the application's API, which instantly deactivates the account. Most applications also revoke active sessions when that happens, but that part depends on the application's implementation.
SAML and SCIM are complementary. SAML provides cryptographic proof of identity at login time. SCIM provides continuous lifecycle management of the accounts that SAML is being used to access. A mature deployment uses both.
The Session Timing Detail
A few notes on how SAML sessions actually behave, since this trips people up:
After the initial assertion is processed, the IDP doesn't keep sending that XML document on every click. The user's browser holds a session cookie. Each time the user accesses a protected resource, the application checks its own session cookie or local session state (or the IDP if the local session has expired).
An application might maintain its own session for two hours while the IDP's session lasts twelve. When the application's two-hour session expires, it usually sends the browser back through the IDP. If the IDP's twelve-hour session is still active, the IDP can issue a fresh assertion without prompting the user to log in again, refreshing the application's session. The user never sees an interruption.
The catch in this architecture: if you change a user's permissions in Active Directory — removing them from a security group, reducing their role — the timing of that change taking effect depends on the IDP, the application, and how authorization data is refreshed. Some systems may continue relying on the user's existing session or previously issued claims until the session expires or the application re-checks authorization. That means a user can sometimes retain access longer than expected, even after their source-of-truth permissions changed.
Part 3 covers OIDC and OAuth 2.0: the four actors, how tokens work, what's inside a JWT, the authorization code flow, PKCE, state and nonce parameters, refresh token rotation, back-channel logout, the auto-discovery document, and Continuous Access Evaluation.
Part 2 of 3 in the SASE, SAML & Modern Identity series.