The Illusion of "One Login to Rule Them All"
Here's the thing about Single Sign-On — it's one of those security controls that sounds like a win for everyone. IT loves it because it reduces password sprawl. Users love it because they stop forgetting fourteen different passwords. Leadership loves it because the vendor sold them on "reduced attack surface." And attackers? Attackers love it because you've just created a single point of catastrophic failure and called it a security feature.
I've watched organizations spend six figures on an SSO rollout, pat themselves on the back at the all-hands, and then proceed to misconfigure the trust relationships so badly that a mid-tier red team was able to pivot from a single compromised service account to every SaaS application in the estate — Salesforce, GitHub, their cloud billing console, all of it — inside forty minutes. The SSO wasn't the problem. The implementation was the problem. There's a difference, and that difference is what attackers are exploiting right now.
Let's get into it.
SAML Is Older Than You Think, and It Shows
SAML 2.0 was finalized in 2005. The threat landscape in 2005 looked nothing like today, and yet here we are, using the same XML-based assertion format to authenticate users into your crown-jewel applications. That's not inherently disqualifying — TLS has older roots and we still use it — but SAML carries implementation baggage that keeps biting people.
The classic one is XML Signature Wrapping (XSW). The SAML response is XML, it's signed, and the signature covers a specific element by reference. The attack? Move the signed element somewhere else in the document tree, inject a malicious element in the position the service provider actually reads, and suddenly the signature validates against the legitimate element while the SP processes your forged assertion. Your IdP never saw that request. The SP thinks it did. Nobody's lying — the XML just has opinions about node ordering that your validator doesn't share.
This is not theoretical. Tools like SAMLRaider (a Burp Suite extension) automate XSW attacks across multiple known wrapping patterns. If your SP implementation is using a naive "find the first NameID element and trust it" approach, you're vulnerable. The fix — validating that the signature covers exactly the element being consumed, not just some element in the document — sounds obvious in retrospect. But I've reviewed SP implementations in the wild, including some built on major enterprise frameworks, that got this wrong.
And then there's the XML canonicalization rabbit hole. Different canonicalization algorithms produce different byte sequences from semantically identical XML. If your verification logic and your consumption logic are using different canonical forms, you can craft inputs where the verification passes on one representation and the consumption happens on another. It's the kind of bug that makes you question your career choices.
Golden SAML: When the Keys to the Kingdom Are in Your AD FS Server
Let me tell you about Golden SAML, because this technique — made infamous by the Nobelium group during the SolarWinds supply chain attack — is something every security practitioner needs to genuinely understand, not just nod along to at conferences.
In a traditional Kerberos environment, a Golden Ticket attack works by forging Kerberos TGTs after compromising the KRBTGT hash. Golden SAML is the same concept ported to federated identity. If an attacker compromises your AD FS server and extracts the token-signing certificate's private key, they can forge SAML assertions for any user, including users that don't exist, including service accounts with elevated privileges, and they can do it entirely offline with no connection to your IdP whatsoever.
The tool that operationalized this is ADFSDump (from the folks at FireEye/Mandiant) and later AADInternals for Azure AD-federated environments. The attack chain isn't complicated: compromise a host with AD FS server role, dump the DKM (Distributed Key Manager) key from Active Directory, decrypt and export the token-signing certificate, then use it to mint assertions. The assertions are cryptographically valid. Your SPs — AWS, Salesforce, whatever — will accept them because they pass signature validation against your legitimate certificate.
What kills me about this is the detection story. If the assertions are forged offline, there's nothing for your IdP to log. You're looking for anomalies in SP-side access logs: logins for users who haven't authenticated recently, logins at unusual hours, logins from unusual source IPs, logins to applications a given user never touches. That's your signal — a forensic absence of IdP telemetry for what should be an IdP-mediated event. Correlating that across your SPs, at scale, in near-real-time? That's actually hard, and most SOC teams aren't set up for it.
The mitigation is partly architectural. Rotating your AD FS token-signing certificates on a meaningful schedule matters, but a forty-five-minute window between compromise and rotation doesn't help you much. The bigger lever is hardening the AD FS infrastructure itself — it should be treated like a Tier 0 asset, equivalent to your domain controllers, with equivalent monitoring, PAW access, and network isolation. Most organizations I've seen treat it like a regular application server. That's a terrible idea.
OAuth Tokens Are Not Sessions, and Treating Them Like Sessions Will Ruin Your Day
OAuth 2.0 and OIDC have largely displaced SAML for modern web and mobile workloads, which is mostly good. But I see a consistent pattern of teams that implement OAuth flows without really internalizing what they've built — and specifically, without thinking hard about token lifetime management.
Here's a scenario I've seen replay itself more times than I'd like. An organization deploys an OIDC-based SSO flow. The access token has a one-hour lifetime. The refresh token has a thirty-day lifetime, because "users were complaining about being logged out." The refresh token is stored in the browser's localStorage. A single XSS vulnerability in any page on that domain gives an attacker persistent, thirty-day access to every API the user can reach. Rotating access tokens provides no protection whatsoever, because the attacker also got the refresh token and can keep rotating themselves.
The access token / refresh token distinction matters here. Access tokens are supposed to be short-lived and scope-limited precisely because they're bearer credentials — whoever holds them gets access, no questions asked. Refresh tokens are longer-lived but should be bound to the authenticated session in some meaningful way. Refresh token rotation (RFC 8707 territory, also baked into OAuth 2.1 draft) helps — each use of a refresh token invalidates the old one and issues a new one, so concurrent use of the same token becomes detectable. But rotation alone doesn't protect you if you're handing refresh tokens to JavaScript that XSS can reach.
The storage question is one of those perennial debates where the "right answer" depends on your threat model. localStorage is synchronously accessible to any script on the page — XSS gets the token trivially. HttpOnly, Secure cookies solve that vector but introduce CSRF risks that you need to mitigate separately. For truly high-assurance applications, a Backend for Frontend (BFF) pattern keeps tokens entirely server-side and issues short-lived, application-specific session cookies to the browser. It's more infrastructure, but if you're protecting access to payroll data or source code, it's the right call.
The Part Nobody Talks About: SP-Initiated vs IdP-Initiated Flows
Most SAML deployments support both SP-initiated and IdP-initiated flows, and the latter is a persistent source of vulnerabilities that flies under the radar.
In SP-initiated flow, the SP generates an AuthnRequest with a nonce (InResponseTo), sends it to the IdP, and expects to get back a response that references that nonce. This is the happy path. The nonce creates a binding between the request and the response, which defends against replay attacks — you can't take a valid assertion from one session and replay it into another because the InResponseTo won't match any pending request.
In IdP-initiated flow, there's no AuthnRequest. The IdP just... sends an assertion to the SP, unsolicited. The SP has no nonce to check against. Some SPs accept IdP-initiated responses but explicitly disable them in their documentation for this reason. Others accept them and don't mention the trade-off. If your threat model includes a compromised or malicious IdP — think supply chain scenarios, think Okta's 2022 Lapsus$ incident where attackers had read-only access to the Okta admin console — IdP-initiated flows become a significant trust boundary concern. A malicious actor with IdP access can initiate authenticated sessions into your SPs without ever touching the legitimate user's browser.
Practically speaking, disable IdP-initiated flows unless you have a specific operational requirement for them. If you absolutely need them, some SPs let you allowlist the specific applications that can receive IdP-initiated assertions. Use that list. Keep it short.
Conditional Access: The Control Everyone Configures Wrong
Conditional Access policies in Azure AD (now Entra ID) and similar constructs in Okta and PingFederate are genuinely powerful controls — when they're configured correctly. In practice, I've done a lot of reviews where the org thinks their Conditional Access is protecting them when it's riddled with holes.
The most common failure mode is exclusion groups. It's operationally convenient to have a group of accounts that bypass MFA enforcement — "break glass" accounts, service accounts, migration accounts. Attackers know this. Hunting for accounts in your Conditional Access exclusion groups is standard practice during Azure AD enumeration, and tools like AADInternals make it straightforward. The exclusion group for your MFA policy shouldn't contain any accounts with access to privileged resources, full stop. If you can't enforce that, that's a process problem that needs solving, not a reason to leave the exclusion group populated with sensitive accounts.
The second failure mode is policy evaluation order and the interaction between named locations, device compliance, and application targets. I've seen organizations that had a seemingly comprehensive policy set where a specific combination of "unmanaged device + legacy auth protocol + specific application" fell through every policy and landed on a default of "grant." Nobody intended that. It emerged from incremental policy additions without holistic review. Running something like What If analysis in Entra ID against your worst-case scenarios (attacker IP, unmanaged device, service account credentials) should be a mandatory part of your change management process for IAM policy modifications. Most teams don't do it.
And legacy authentication. Drives me absolutely nuts. If you still have any conditional access exclusions for legacy authentication protocols — Basic Auth to Exchange, NTLM-based flows — you're leaving a door open that attackers are actively walking through. Microsoft started blocking legacy auth by default in 2023, but organizations that were grandfathered in or exempted themselves still exist. Password spray against Basic Auth endpoints is boring, effective, and doesn't trip most MFA policies because it never reaches the authentication layer where MFA is enforced. There's no good reason to keep it enabled.
Session Tokens Are the Last Mile, and They're Getting Stolen
Even if you've implemented your SAML flows correctly, your OAuth tokens are properly scoped and stored, and your Conditional Access policies are airtight — you're still exposed to session token theft via adversary-in-the-middle proxies, and this attack class is having a moment.
Tools like Evilginx2 and Modlishka operate as reverse proxies that sit between the user's browser and the legitimate IdP. The user authenticates normally, completes MFA, and the phishing proxy captures the resulting session cookie or token. The attacker replays that token from their own browser. Your Conditional Access policy saw a valid MFA completion — it never knew the session was being proxied. This is why device-binding controls matter. FIDO2/WebAuthn hardware tokens are resistant to this attack class because the cryptographic response is bound to the legitimate origin domain; a proxy serving a different domain can't satisfy the challenge. Software TOTP and push-based MFA? Not resistant. The session gets stolen after a valid MFA ceremony.
Conditional Access policies that enforce Continuous Access Evaluation (CAE) in Entra ID help shrink the window here — if a session token is used from an IP that's been flagged, CAE can force re-authentication within minutes rather than waiting for token expiry. It's not a complete mitigation, but it meaningfully degrades the attacker's ability to use a stolen session token for extended persistence.
The honest assessment is that no SSO implementation is breach-proof. The goal is to make the implementation hard to abuse, quick to detect, and designed so that a compromise of one layer doesn't cascade into full-estate access. That means defense in depth inside your IAM architecture — not just "we have SSO with MFA" and calling it done. Segment what each SP can access. Monitor for the forensic signals Golden SAML leaves behind. Treat your AD FS or Okta tenant as Tier 0 infrastructure. And for the love of everything, test your SAML implementations with SAMLRaider before an attacker does.


