How to test SSO login: it's an access-control test, not a happy path

How to test SSO login the way it actually fails: SP- vs IdP-initiated flows, an SSO-enforced org that can still fall back to a password, a deprovisioned user who keeps access, and wrong-domain email. Plain-English prompts, no SAML library required.

playbookssoauthenticationsecurity-testingno-codeqa

The SSO demo everyone tests is the one that works: click "Sign in with SSO," bounce to Okta, come back logged in, done. It's the least interesting thing about SSO. Single sign-on isn't really a login feature — it's an access-control feature wearing a login screen, and every bug that actually matters lives in the gap between "the user signed in" and "the right user has exactly the access they should." A password login has one question: are you who you say you are. SSO has five more: which flow did you come in through, can you sneak past SSO with an old password, did your access actually end when IT deprovisioned you, is your email domain even allowed, and did you land with the right role. None of those is visible in the happy path, and all of them are testable from a browser without touching a SAML library.

That last part matters, because most guides on how to test SSO login are really guides on how to configure it — Okta setup, ACS URLs, certificate fingerprints — or they hand you a SAML-decoder browser extension and call it testing. Configuration is not verification, and there's an honest boundary here worth stating up front.

The honest limit, stated first

A browser agent tests SSO the way a user or an attacker experiences it: it drives the flow, lands (or doesn't) in the app, and checks what access the resulting session actually has. What it does not do is crack open the signed SAML assertion and validate the XML signature, the certificate, or the NotOnOrAfter conditions inside it. That's real work, and it has real tools — Okta's own guide points you at SAML-tracer and similar browser add-ons precisely because "SAML responses are signed," and inspecting that signed payload is assertion-crypto territory. If your worry is a forged or replayed assertion, you want SAML-tracer, your IdP's system log, and a security review — not this playbook.

What this playbook covers is the larger and more commonly broken half: the outcome of the handshake. Does the right person get in, does the wrong person stay out, and does the session carry the correct identity and role. That's a behavioral question you can assert on from the outside, and it's exactly the half that scripted suites skip because it requires thinking like someone misusing the system — the same reason an agent finds the bugs a script walks past.

Both directions of the handshake

SSO has two flows and they fail differently, so testing one proves little about the other. Okta's guide draws the line cleanly: in the SP-initiated flow, the user starts at your app, clicks sign-in, and your app "takes you to the Okta sign-in page if you aren't already logged in" — your SP asks the IdP, the IdP authenticates, and a signed response comes back to your assertion consumer service. In the IdP-initiated flow, the user starts at the IdP dashboard and clicks your app's tile, so the IdP "sends a SAML response" to your app that your app never asked for.

That second one is where teams get burned, because your app receives an assertion with no prior request of its own to correlate it against. If you only ever tested by clicking the button inside your app, you have never exercised the IdP-initiated path at all — and it's the one that's live the moment your app appears on anyone's Okta dashboard. Test both directions, and treat "works from our login page" and "works from the Okta tile" as two separate results.

The bypass that makes SSO pointless

Here's the bug that turns an entire SSO rollout into theater: an org has SSO enforced — meaning members are supposed to authenticate only through the IdP — and yet the old email-and-password form still works. Someone navigates directly to /login instead of the SSO entry point, types the password they set before SSO was turned on, and sails in, completely bypassing the IdP your customer's security team believes is the only door.

This is the SSO equivalent of the force-browse bug, and it's common because "add SSO" often means "add an SSO button" without "remove every other way in." Enforcement is a negative requirement — the password path must be gone for that org — and negative requirements are the ones nobody writes a happy-path test for. If your customer bought SSO for their compliance story, this bug is the whole story failing silently.

What "testing SSO login" actually has to cover

Beyond the two flows and the bypass, here's the surface that the green happy path hides:

  1. SP-initiated login lands authenticated with the correct identity — the session shows the SSO user's real name and email, not a blank or a stale profile.
  2. IdP-initiated login lands authenticated from the IdP tile, independently of the SP-initiated path.
  3. Enforcement holds: for an SSO-enforced org, the password form and any other legacy entry point are refused, not merely hidden behind a redirect.
  4. Deprovisioning actually revokes access. When a user is removed from the app in the IdP, their next attempt must fail — and any existing session should not sail on indefinitely. This is the one IT assumes works and rarely checks.
  5. Domain restriction rejects outsiders. If SSO is scoped to @customer.com, an @gmail.com or @attacker.com identity must be refused, cleanly, not 500'd.
  6. Group-to-role mapping is honored. A user in the IdP's "admin" group lands as an admin; a user in "member" lands as a member and cannot reach admin surfaces. SSO doesn't just say who you are, it often says what you can do.

A scripted suite almost never covers 3 through 6, and the reason is structural: the script was written to prove the intended login works, and every one of these is about a login that's supposed to fail or to land with constrained access. You have to think like the person the control is meant to stop.

Prompt 1: both directions, and the identity that came through

Test SSO login on https://staging.yourapp.com in both directions.

SP-initiated:
1. Start on the app's login page and choose "Sign in with SSO".
2. Complete the IdP login as sso-user@customer.com / <staging IdP password>.
3. Confirm you land authenticated and the account shows the correct
   name and email for that user — not blank, not a different user.

IdP-initiated:
4. In a fresh session, start from the IdP dashboard tile for this app
   (or the app's IdP-initiated entry URL) and click through.
5. Confirm you land authenticated as the same user, with the same
   correct identity shown.

Report, for each direction: whether login succeeded, the identity the
app displays afterward, and any error page, loop, or blank state. Flag
any case where one direction works and the other fails, or where the
displayed identity is wrong.

Use scenario-specific credentials for the staging IdP user here — a Test Scenario takes its own credentials, so the real SSO login runs end to end. The agent reads the identity the app actually renders after the handshake, so "logged in as the wrong person" — a real assertion-mapping bug — comes back as a failure instead of a green check.

Prompt 2: the enforcement bypass and the wrong domain

Test SSO enforcement and domain restriction on
https://staging.yourapp.com.

Password bypass (org has SSO enforced):
1. Go directly to the password login URL (e.g. /login) rather than the
   SSO button.
2. Try to sign in as sso-user@customer.com with a known password.
3. Confirm this is REFUSED — an SSO-enforced org must not accept a
   password login. Report exactly what happens: refused with a clear
   message, silently redirected to SSO, or actually logged in.

Wrong domain:
4. Attempt SSO (or password) login with an email outside the allowed
   domain, e.g. outsider@gmail.com.
5. Confirm it is refused cleanly with a sensible message — not a 500,
   not a stack trace, not a partial login.

Flag it as a serious finding if the password form logs the user in on
an SSO-enforced org, or if a disallowed domain gets in or errors
ungracefully. Capture the HTTP status and screen for each attempt.

Step 3 is the one to run before you tell a customer SSO is enforced. "Silently redirected to SSO" is a pass; "actually logged in" is the finding that undoes the entire feature, and the two are one line apart in behavior.

Prompt 3: deprovisioning and role mapping

Test SSO deprovisioning and role mapping on
https://staging.yourapp.com.

Deprovisioning:
1. Confirm removed-user@customer.com can currently log in via SSO.
2. (Have the IdP admin remove that user's access to this app, or use a
   user already removed in the IdP.)
3. Attempt SSO login again as that user. Confirm access is now REFUSED.
4. If that user still had an active session before removal, try to load
   a protected page in it and confirm the session no longer works.

Role mapping:
5. Log in via SSO as a user in the IdP "admin" group; confirm admin-only
   pages and actions are available.
6. Log in via SSO as a user in the "member" group; confirm admin pages
   are refused (403/redirect, never shown) and only member surfaces work.

Report each result. Flag any removed user who still gets in, any session
that outlives deprovisioning, or any member who can reach admin surfaces.

Step 4 is the quiet one. Revoking future logins is easy; killing the existing session is the part teams forget, and it's the same server-side-invalidation question the session-timeout playbook chases — a session that outlives the user's access is a session that was never really tied to their access. Steps 5 and 6 are pure access control: SSO that authenticates everyone as the same role is a convenience feature, not a security one, and the difference is a direct-URL check away — the same cross-privilege reasoning the multi-tenant access-control playbook uses to prove one account can't reach another's data.

Reading the results

Each run comes back as a Monito Session: a screenshot timeline of every redirect and landing state across both flows, plus the network log with the actual requests and status codes. For SSO the network log is where the truth is — a protected page that returns 200 with real data for a deprovisioned user is provably a failure, and a clean 403/302 is provably a pass, regardless of what the UI seems to show. The screenshot timeline is where the bypass and wrong-identity bugs surface: you can see the password form accept a login it should have refused, or the app render the wrong name after the handshake.

None of the prompts names a selector or a SAML field — only the behavior. Login and SSO configuration screens get rebuilt constantly, and a test that describes "an enforced org must refuse the password form" survives every redesign, which is why describing intent beats scripting steps for anything as security-sensitive and change-prone as authentication. It's the same durability the magic-link auth playbook and the signup-flow playbook rely on.

The one to run right now

If you run a single check today, run the enforcement bypass — it's the property that decides whether "we have SSO" is true or a compliance liability.

On https://staging.yourapp.com, the org customer.com has SSO enforced.
Go directly to the password login URL (e.g. /login), not the SSO button,
and attempt to sign in as sso-user@customer.com with a known password.
The correct result is a clean refusal (or a forced redirect into SSO with
no password login accepted). Report exactly what happens, the HTTP status,
and whether you ended up authenticated. Then, in a fresh session, attempt
login with an out-of-domain email like outsider@gmail.com and confirm it
is refused cleanly rather than logged in or 500'd. Capture screenshots and
all network activity for both attempts.

Save it as a Test Scenario, pin it to your staging Project, and wire it into CI on every deploy that touches auth, middleware, or your IdP config — the surfaces where an enforced org quietly stops being enforced. A full run is typically 8–13 credits, roughly $0.08–$0.13, and your first run is free. One honest caveat, restated: this proves the handshake's outcome — who gets in and with what access — not the cryptographic validity of the SAML assertion itself; for the signed-payload internals, keep SAML-tracer and a security review in the loop. Treat this as the every-deploy proof that the right people get in, the wrong people stay out, and SSO is still actually the only door.

All Posts