SSO and federation
SSO federates login to an IdP, but the SP still owns verification on the way in (signature, issuer, audience, expiry) and session teardown on the way out — and revocation across a federation is only ever eventually consistent.
A customer files a ticket: a contractor they offboarded in their corporate directory a week ago is still logged into your SaaS and downloading reports. You check your code — login works, the “Sign in with your IdP” button is green, federation is configured exactly as the vendor docs said. And yet a disabled account is walking around inside your app seven days later. Nothing is “broken” in the sense of throwing an error; the IdP authenticated this person on day zero, your app believed it, minted a session, and then forgot the IdP existed. That gap — between an identity getting revoked upstream and your app noticing — is the part of SSO that the integration tutorial never mentions and the incident review always finds. “We use SSO” is the beginning of the auth conversation, not the end.
By the end of this lesson you’ll know exactly what the IdP does for you, what it pointedly does not, the four checks an SP still owns on every token, and why revocation across a federation is eventually consistent rather than instant.
What federation actually delegates — and what it doesn’t
Single sign-on lets a user authenticate once with a central Identity Provider (IdP) — Okta, Entra ID, Google Workspace, a customer’s own directory — and then access many Service Providers (SPs): your apps. Federation is the trust arrangement that makes one organization’s IdP an authority your SP will believe. The two dominant protocols are SAML (XML assertions, enterprise-heavy) and OpenID Connect / OIDC (a JWT-based identity layer on top of OAuth 2.0). Different wire formats, same shape: the IdP authenticates the user and hands your SP a signed statement — a SAML assertion or an OIDC ID token — that says “this subject authenticated, here are their claims.”
Here is the precise thing federation delegates: the act of authenticating the user. The IdP runs the password check, the MFA prompt, the passkey ceremony; you never see the credential. What federation does not delegate is everything around that statement. The SP still has to verify the statement is genuine and meant for it, decide how long to trust it locally, and tear down that local trust when the user is gone. The mental model that causes outages is “we outsourced auth to the IdP.” You outsourced the credential check. You still own verification, session lifetime, and logout.
The four checks the SP still owns on every token
When the ID token arrives, “the IdP signed it” is necessary but nowhere near sufficient. A senior treats verification as four separate gates, and passing three of them is still a hole.
- Signature — confirm the token was signed by the IdP you trust, using the IdP’s published key (the JWKS endpoint for OIDC, the SAML metadata certificate). This answers “did my trusted IdP produce this?” Critically, pin the algorithm out-of-band to what you agreed at federation setup (e.g. RS256) and never trust the
algfield in the token’s own header — the classic algorithm-confusion attack downgrades RS256 to HS256 and signs with the IdP’s public key (which is, by definition, public) used as the HMAC secret, and a naive verifier accepts it. - Issuer (
iss) — confirm the token came from the specific IdP you federated with, not merely an IdP whose key you happened to fetch. Without this check, a token minted by any issuer you can reach is waved through. - Audience (
aud) — confirm the token was minted for you. The audience claim binds a token to a specific SP’s client ID. This answers a different question than the signature: “was this produced for me?” In a multi-SP federation it matters enormously — a genuine, correctly-signed token issued for sibling app SP-A can be replayed to SP-B, and if SP-B skips the audience check it accepts it as a valid login. - Expiry (
exp) — confirm the token is still in its validity window. A captured token with no expiry check works forever.
Signature answers “who signed it”; issuer, audience, and expiry answer “from whom, for whom, and still valid?” They are four checks, not one.
▸Common mistake
The SAML equivalent of algorithm-confusion is XML Signature Wrapping (XSW). The flaw is an ordering bug: code reads the user’s identity from “the first <Subject> it finds” and then, separately, asks the library “is the document’s signature valid?” — and both succeed. An attacker injects a second <Subject> that the app reads, while the valid signature still covers the original element. Verifying that a signature is valid is not the same as verifying it covers the exact bytes you acted on. The fix is to use a vetted SAML library correctly so that signature validation and identity extraction are bound to the same element — never hand-roll the XML parse, and never read identity from anything but the signed, verified node.
The deprovisioning gap: federation federates login, not logout
This is the failure mode in the Hook, and it is structural, not a typo. The IdP’s assertion is a point-in-time fact: “this subject authenticated at 09:14 on day zero.” Your SP verifies it and mints a local session — and that session is now yours, decoupled from the IdP. When the customer disables the user on day zero, nothing pushes that change to your session store. The login was a pull (the SP pulled an assertion when the user arrived); deprovisioning needs a push (the IdP tells the SP “tear this down”), and the basic login flow has no such channel. If your local session lives 30 days, the disabled user keeps working for up to 30 days, because they never have to return through the IdP — which would now refuse them.
There is no clean fix that makes external revocation instant, and pretending otherwise is the trap. You cannot call the customer’s IdP on every request — that adds latency to every page load and makes their outage your outage. You cannot mirror their full directory locally — that drifts, and it re-owns the deprovisioning you federated precisely to outsource. So the senior move is to bound the window and accept that revocation is eventually consistent:
- Short SP session lifetimes (minutes to a few hours, not days) so a deprovisioned user is forced back through the IdP soon and gets refused. This caps routine stale access at the session length.
- An active revocation channel where the protocol allows it: OIDC back-channel logout or SAML Single Logout, so an IdP that supports it can call your logout endpoint and tear down sessions immediately — treated as best-effort, because not every IdP implements it and networks fail.
- Step-up re-validation for sensitive actions (payments, role changes, data export): re-check the IdP or require a fresh
auth_timeat action time, so the highest-impact paths get near-real-time enforcement without phoning the IdP on every read. - State the residual window plainly in your security docs and customer contract: the gap between upstream disable and SP session expiry is non-zero. That is an owned, communicated property — and tightening session lifetime is the dial a customer can ask you to turn.
| Concern | What the IdP owns | What the SP still owns |
|---|---|---|
| Credential check | Password, MFA, passkey ceremony | Nothing — fully delegated |
| Token trust | Signs the assertion / ID token | Verify sig, iss, aud, exp — pin the algorithm |
| Local session | Nothing after the redirect | Lifetime, cookie, where the truth lives |
| Revocation | Disables the user; may push logout | Bound the window; wire back-channel logout |
| Signature failure mode | — | alg-confusion (JWT) / XML Signature Wrapping (SAML) |
How a senior reasons about any “Log in with…” integration
Make this the standing question for every federation you set up: when this identity is revoked upstream, how long does my downstream session outlive it — and is that window owned and communicated? Then run a fixed checklist on the way in and on the way out: pinned issuer and key, audience scoped to this SP, signature with a pinned algorithm, expiry checked, short session lifetime, back-channel logout wired where the IdP supports it, and step-up re-validation on sensitive actions. Treat revocation as eventually consistent by default. The SP never stops owning verification on the way in and teardown on the way out — federation moved the credential check, not your responsibility for the seam.
Your multi-tenant SaaS federates with each customer's own IdP. Customers expect that disabling a user 'quickly' stops access. How do you bound the stale-access window?
An OIDC ID token's signature verifies against the IdP's JWKS and it hasn't expired. Why is that still not enough to log the user in?
Why must you pin the expected signing algorithm out-of-band instead of trusting the alg field in the token header?
Order the steps of correctly turning an OIDC callback into a trusted local session:
- 1 Exchange the authorization code for an ID token at the IdP's token endpoint
- 2 Verify the signature against the IdP's JWKS with the algorithm pinned out-of-band (e.g. RS256)
- 3 Validate iss is your IdP, aud is this SP's client ID, and exp is in the future
- 4 Mint a short-lived local session and wire back-channel logout so the IdP can tear it down
- 01Federation delegates authentication to the IdP. Name precisely what the SP still owns, and why.
- 02Explain the deprovisioning gap and how you bound it without making your availability depend on the customer's IdP.
Single sign-on lets a user authenticate once with a central IdP and reach many SPs, over SAML or OIDC — but federation delegates only the credential check. The IdP runs the password, MFA, or passkey ceremony and hands your SP a signed statement; everything else stays yours. On the way in, the SP owns four separate verification gates: signature (with the algorithm pinned out-of-band, never trusting the token’s own alg header, or you invite RS256→HS256 algorithm confusion; the SAML analogue is XML Signature Wrapping), issuer, audience (so a token minted for a sibling SP can’t replay), and expiry — because “the IdP signed it” answers who signed it, not from-whom-for-whom-and-still-valid. On the way out, the SP owns teardown, and this is where SSO quietly breaks: federation federates login (a pull) but not logout (a push), so when an identity is revoked upstream nothing reaches your session store, and a long-lived local session keeps a disabled user inside your app. You can’t make external revocation instant, so you bound the window — short session lifetimes, OIDC back-channel logout or SAML Single Logout as best-effort, step-up re-validation on sensitive actions — and you state the residual gap rather than pretend it’s zero. The standing question for every “Log in with…” integration: when this identity is revoked upstream, how long does my downstream session outlive it, and is that window owned and communicated?
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.