SSO & Federation
SAML and OIDC — how the SP trusts the IdP to vouch for users, and the four ways that trust breaks.
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):
- SP → IdP —
AuthnRequest(the user's browser carries a 302 to the IdP) - IdP → IdP — user authenticates (password, MFA, whatever)
- IdP → SP — SAML Response POSTed to the SP, with a signed
<Assertion> - SP → SP — verify signature, audience, timestamps,
InResponseTo - SP → SP — issue session cookie
OIDC Authorization Code + PKCE (6 legs):
- SP → IdP —
/authorize?response_type=code&code_challenge=…&nonce=… - IdP → IdP — user authenticates
- IdP → SP — redirect with
?code=… - SP → IdP —
POST /tokenwith the code + PKCE verifier - SP → SP — verify signed ID token (
iss,aud,exp,nonce, signature against JWKS) - 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 skew —
NotOnOrAfter(SAML) orexp(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/audclaim 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.comshouldn't work atapp-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.