When using SAML, you have two methods for starting Single Sign-On (SSO): SP-initiated or IdP-initiated. Both have their use cases, but one is more secure than the other. No points for guessing from the title.
These flows are used entirely within the browser and defined by SAML’s Web SSO profile, which is the main use case of modern SAML (SAML in the 2020s). The issues raised in this article apply to all three binding types: redirect, post, and artifact.
IdP-Initiated SSO vs. SP-Initiated SSO
SP-initiated SSO involves the Service Provider (SP) creating a SAML request and redirecting both the user and the request to the identity provider. Once the identity provider has validated the request, and the user has authenticated at the identity provider, the user is redirected back to the service provider along with a SAML response and assertion issued by the identity provider. Once the service provider validates the response, it can start its own session for the user. SP-initiated SSO could be initiated by a login button within the service provider or when the user tries to access a protected area.
IdP-initiated SSO involves an authenticated user clicking a button in the Identity Provider (IdP) and being redirected to the service provider along with a SAML response and assertion. The service provider is expected to accept the response and start a session for the user. This flow would typically be initiated by a page within the IdP that shows a list of all available SPs that a user can use. Another common use case of this flow is to allow users to bookmark the IdP login page.
When using SP-initiated SSO, a modern SAML solution will do the following:
- Generate a request ID and include it in the SAML request message
- Generate a relay state (either opaque/random application state or just as a simple CSRF mechanism) and include it in the SAML request URL
- Securely store the two values before redirecting to the IdP (think a cookie or a server-side cache)
When the service provider receives a SAML response, it will fetch the stored request ID and relay state values and then check them against the SAML responses
InResponseTo value and relay state, respectively.
The use of request ID and relay state validation allows the service provider to verify that it was expecting a response/assertion and prove that the response it received was generated in response to its request.
This validation procedure is similar to the OpenID Connect usage of the state and nonce parameters. Like all good security protocols, SP-initiated SSO makes use of a request and a matching response.
When using IdP-initiated SSO, you do not get the same assurances as SP-initiated SSO. Instead, the service provider receives an unsolicited SAML response and assertion, losing any protocol mechanism that allows it to detect whether that message has been stolen or replayed.
Why IdP-initiated SSO is insecure
IdP-Initiated SSO is highly susceptible to injected assertions, where an attacker steals a SAML assertion and injects it into the service provider. With this stolen SAML assertion, an attacker can log into the service provider as the compromised user, gaining access to their account, something that would otherwise be much harder with SP-initiated SSO.
This leaves service providers in a tricky place. A service provider can see that the message and assertion are valid since it was issued by the expected issuer and signed with the expected key, but it cannot verify that a malicious party did not steal & use the assertion.
There are many ways to intercept the SAML response & assertion. For example, it could be some sort of Man-In-The-Middle (MITM) attack, an open redirect attack taking advantage of improper endpoint validation, via leaky logs and headers, or even via browser-based attacks.
Another common complaint regarding IdP-initiated SSO is that it can overwrite existing sessions within a service provider. However, this is more often an implementation detail rather than a limitation in the protocol’s approach.
Doesn’t TLS or XML signing defend against this?
No, not at all. The stolen message will be legitimately signed and optionally encrypted by the identity provider. The attacker isn’t tampering with the message in any way; they are simply taking it from one browser session and using it in another.
Transport Layer Security (TLS, HTTPS) also doesn’t help you here. Since the legitimate flow would involve a redirect from the identity provider to the service provider, transport layer security only tells you that the connection from the browser to the service provider is secure. There is no connection from the identity provider to the service provider.
This applies to all SAML Web SSO binding types. The service provider cannot distinguish between a legitimate user being redirected with the SAML response or the attacker.
Modern IdP-initiated SSO
You can try to mitigate some of the flaws in this flow using the following techniques; however, bear in mind that making it as secure as SP-initiated SSO is impossible.
Follow the specification
Section 4.1.5 (Unsolicited Responses) of the SAML 2.0 profiles specification states that a service provider must ensure that any unsolicited SAML responses received do not contain an
This at least prevents responses generated using SP-initiated SSO from being stolen and injected via the vulnerable IdP-initiated SSO flow.
Use the minimum possible response trust length
When validating SAML responses and assertions, you validate when they were issued and when they expire. The identity provider can state when the message expires, but the service provider also gets a say. So, make sure that’s as low as possible. In most cases, the response will be generated by the identity provider and then immediately validated by the service provider. That shouldn’t take more than a few seconds. This is a similar approach to the lifetime of OpenID Connect’s identity token.
Unfortunately, clock skew between the IdP and SP can be an issue with this validation (it has been for many of my customers), so you’ll need to account for that; however, you shouldn’t be talking more than a few minutes.
SAML IdP-initiated SSO definitely has its flaws; however, by adopting some of the lessons learned from modern applications and protocols, you can try and mitigate some of these concerns. That being said, unfortunately, you cannot prevent assertion theft and injection, but you can at least stop replay attacks.
These mitigations do push yet more complexity onto the service provider to combat the identity provider’s limitations. In the past, I’ve seen some articles advocate IdP-initiated SSO due to the fact that an SP might not be capable of creating requests, but now instead, they must start caring about replay attacks and accept the risk of injected SAML messages. This is more complex than creating a SAML request and, in my opinion, much harder to do in a load-balanced environment than simply remembering a relay state in the context of the browser. If your app just can’t create a SAML request, consider using a modern SSO protocol. It’s 2021, people.
It’s bizarre to me that IdP-initiated SSO is so prevalent, seeing as only 3 short paragraphs are dedicated to it in the SAML 2.0 specifications (titled “Unsolicited Responses”). It may be that many people migrating from SAML 1.0 or SAML 1.1 saw this as the logical next step, but it’s been over a decade since these specifications were made obsolete. If you’ve never heard of SP-initiated SSO, take a look at SAML’s Web SSO specification. It’s pretty much the only thing in there.
If you think injection isn’t an issue, take a look at the amount of work happening in OAuth and OpenID Connect to lock down all instances of code or token injection. Unsolicited security data is a real risk for all protocols, no matter their age.