Skip to content
All articles
Security11 August 2026·7 min read

IdP-initiated SSO, and what it quietly gives up

Customers ask for the tile in their app launcher. What it removes is the only link between a sign-in attempt and the browser that started it.

Paycux security engineering

There are two ways a SAML sign-in can begin. In the ordinary one, the person arrives at your application, you send them to their identity provider with a request, and the response comes back carrying a reference to that request. In the other, the person clicks a tile in their provider's launcher and an assertion arrives at your endpoint with no preceding request at all.

Customers ask for the second constantly, because the launcher is where their staff start their day. It is a reasonable request, and granting it removes a property most implementations were quietly relying on.

What the request was doing for you

In the service-provider-initiated flow, you generate a request identifier, remember it against the browser session, and refuse any response whose InResponseTo does not match. That single check tells you the response belongs to this browser, at this moment, in reply to something you asked for. It is CSRF protection for the login endpoint, and it is free.

Unsolicited assertions have no such field, by design. Your endpoint is now accepting a credential-bearing POST from an anonymous source, which means anyone who can obtain a valid assertion — including the legitimate holder of one, acting maliciously — can cause a browser that is not theirs to be signed in as somebody else.

An unsolicited assertion turns your login endpoint into a POST that anyone can cause a browser to make.

The attack is boring and effective

Login CSRF gets dismissed as low severity because the attacker is signing the victim into an account rather than out of one. The consequences depend entirely on what your product does with a session. If the victim then uploads a document, connects an integration or saves a payment method, they have done it inside an account the attacker controls.

The second lever is the relay state parameter, which exists to carry a deep link through the handshake. It is attacker-supplied text that your application will redirect to, which is an open redirect unless you constrain it. Treat it as an index into paths you consider valid, not as a URL.

  • Make unsolicited assertions opt-in per connection, not a global fallback
  • Keep the replay cache regardless — it is the only defence left once InResponseTo is gone
  • Accept relay state only as a relative path matching an allowlist pattern
  • Log the flow type on the session so an investigation can tell them apart
  • Never allow an unsolicited assertion to satisfy a step-up or re-authentication prompt

A middle path that keeps the tile

You can give the customer their launcher tile without accepting an unsolicited assertion as a full sign-in. Point the tile at an endpoint on your side that starts a normal service-provider-initiated flow. The person clicks in the launcher, bounces through the provider, and comes back with a correlated response. To them it is one click; to you it is the flow you already trust.

Where the provider cannot be configured that way, land the assertion on a page that establishes a fresh browsing context before granting the session — a short interstitial the person confirms. It costs a click on a path most people take once a day, and it restores the property that a session belongs to the browser that asked for it.

Everything here, already built

Sign-in, enterprise SSO, directory provisioning, roles and an audit trail behind one API. Start with the quickstart and have a working sign-in this afternoon.

Start selling to enterprise customers

Create an account, point sign-in at Paycux, and get back to the part of the product that is actually yours.