it · level 2

SSO & Federation

SAML and OIDC — how the SP trusts the IdP to vouch for users, and the four ways that trust breaks.

200 XP

SSO & Federation

One login, every app. That's the promise — and the reason your company probably uses Okta, Microsoft Entra ID (formerly Azure AD), Google Workspace, Auth0, or Ping. Under the hood it's two protocols (SAML and OIDC) and one idea: the application trusts a separate identity provider to vouch for who's signing in.

This level is about what "vouch" means on the wire, why the trust anchor is a cryptographic signature, and the four ways that trust gets broken in practice.

Analogy — the doorman with a signed list

Imagine a building with a dozen doors, each guarded by a doorman, and one security desk that actually knows the employees. Instead of every doorman learning every face, the security desk hands out signed passes. The doorman doesn't have to recognise you — they just have to recognise the security desk's signature.

The doorman is the Service Provider (SP) — the app you're logging into. The security desk is the Identity Provider (IdP) — Okta, Entra, Google. The signed pass is the SAML assertion or OIDC ID token.

If the signature on the pass is good, you walk in. If not, you don't. That's federated identity.

SAML vs OIDC

Same idea, different wire format.

SAML 2.0 OpenID Connect (OIDC)
Payload XML <Assertion> with XMLDSig JSON Web Token (JWT)
Delivery Browser-posted HTML form 302 redirect + /token exchange
Signing XML digital signature JWS (HS256 / RS256 / ES256)
Common in Enterprise SaaS, on-prem Modern web + mobile apps
Example vendors Okta, Ping, ADFS Auth0, Google, Entra, Okta

SAML is older and heavier; OIDC sits on top of OAuth 2.0 and is the default for anything green-field. Large enterprises usually speak both. The core flow is isomorphic — a signed artefact travels from IdP to SP, and the SP decides whether to trust it.

SP-initiated vs IdP-initiated

SP-initiated is what you expect: you type app.example.com, the app redirects you to your IdP, you log in, the IdP sends you back with a signed assertion. The playground models this.

IdP-initiated is the "launchpad" experience: you open your Okta tile grid and click an app. No outbound request from the SP — the IdP just POSTs a signed assertion to the SP. Convenient, but it defeats CSRF protection (there's no in-flight request for InResponseTo to match), which is why modern best practice prefers SP-initiated.

The redirect chain, leg by leg

SAML (5 legs):

  1. SP → IdPAuthnRequest (the user's browser carries a 302 to the IdP)
  2. IdP → IdP — user authenticates (password, MFA, whatever)
  3. IdP → SP — SAML Response POSTed to the SP, with a signed <Assertion>
  4. SP → SP — verify signature, audience, timestamps, InResponseTo
  5. SP → SP — issue session cookie

OIDC Authorization Code + PKCE (6 legs):

  1. SP → IdP/authorize?response_type=code&code_challenge=…&nonce=…
  2. IdP → IdP — user authenticates
  3. IdP → SP — redirect with ?code=…
  4. SP → IdPPOST /token with the code + PKCE verifier
  5. SP → SP — verify signed ID token (iss, aud, exp, nonce, signature against JWKS)
  6. SP → SP — issue session cookie

The verification leg is the heart of both protocols. Everything else is transport.

Why signature verification is the trust anchor

The SP never sees the user's password. It never calls the IdP back to double-check. The only thing standing between a legitimate login and a forged one is the signature on the assertion / ID token.

If an attacker can forge a signed assertion that claims Subject=alice@corp.example, they are Alice as far as the SP is concerned. So the signature check must:

  • Use the right public key — pinned cert for SAML, JWKS fetched from the IdP's well-known URL for OIDC
  • Reject alg: none — a notorious OIDC gotcha where libraries accept unsigned tokens
  • Require the signature element — SAML SPs have historically been fooled by assertions without a signature block at all

Four ways this breaks

The challenge on this level seeds one of these failure modes; you have to spot which leg the SP should refuse on.

  • Bad cert / key rotation — the IdP rotated signing keys and the SP's pinned cert (or JWKS cache) is stale. Signature fails verification.
  • Clock skewNotOnOrAfter (SAML) or exp (OIDC) is in the past relative to the SP's clock. Typical cause: ops never configured NTP on a VM.
  • RelayState / InResponseTo tamper — a man-in-the-middle rewrites the round-trip identifier. The SP's CSRF check fails before it even gets to the signature.
  • Wrong audience — the assertion was minted for a different SP. Accepting it would be a confused-deputy bug; the AudienceRestriction / aud claim must match the SP's own entityID / client_id.

For OIDC specifically, nonce mismatch is the parallel failure to InResponseTo — it's how the SP ties the ID token back to the original /authorize request and blocks token replay.

Common gotchas

  • Forgetting to verify InResponseTo — SAML SPs sometimes accept any signed assertion, even if they didn't ask for one. That's the IdP-initiated antipattern in miniature.
  • Accepting unsigned assertions — SAML allows signing the response, the assertion, or both. Many libraries default to "check if there's a signature; if not, allow". That's a critical misconfiguration; require a signature on the assertion itself.
  • Not validating audience — an assertion for app-a.example.com shouldn't work at app-b.example.com. Without the audience check, a malicious SP admin can replay assertions they received against other SPs sharing the same IdP.
  • Caching JWKS forever — OIDC IdPs rotate keys. SPs must re-fetch JWKS on key-id miss; pinning is a SAML habit that doesn't map.

Playground

Pick a scenario, advance leg-by-leg. The payload panel shows the wire contents at each step; the signed-indicator lights up on the legs where a cryptographic signature is present. Toggle the protocol to see the SAML vs OIDC flows side-by-side, and switch between the happy path and the failure modes to see exactly what the SP is looking at when it decides to refuse.

Visualizer

The sequence diagram draws Browser / SP / IdP as three lanes with horizontal arrows for each leg, playhead advancing top-to-bottom. Broken legs render in warning colour — the SP's "refuse and log" point is the bright band in the trace.