OAuth 2.0, OIDC, and SAML: Three Protocols, Two Questions, One Expensive Mistake
I have spent a sizeable chunk of my career inside other people's identity stacks. Federating a campus identity provider across 150-odd universities. Entitling nearly 19 million Apple TV subscribers through Ping for an F1TV launch. Sitting in design reviews where a very senior engineer says "we'll just use OAuth for login" and the room nods.
That last one is the reason for this post.
Because here is the thing nobody says plainly enough: two of these three protocols answer the same question, and the third answers a different question entirely. Almost every bit of confusion — and a specific, recurring class of vulnerability — comes from not seeing that split.
Let's fix that first, then go deep on each.
The split
Read that middle column again.
OAuth 2.0 is not a login protocol. It was never designed to be one. The specification is titled The OAuth 2.0 Authorization Framework, and the word "authentication" appears in it mostly to warn you off. It answers exactly one question: what is this application permitted to do with this user's stuff?
SAML and OpenID Connect both answer a different question: who is this person?
So the real lineup isn't "three competing SSO protocols." It's two authentication protocols from different eras, and one authorization protocol that one of them is built on top of.
OAuth 2.0: a valet key for your data
The analogy that has never failed me in a design review is the valet key.
Some cars ship with a second key that starts the engine and opens the driver's door, but won't open the trunk or the glovebox. You hand it to a stranger in a parking garage. You are not proving who you are — the valet already has you standing in front of him. You are granting a narrow, revocable capability to someone you only partly trust.
That is OAuth, exactly.
Walk the flow once and the design intent is obvious. PrintShop wants your photos. It never sees your Google password — you type that at Google, on Google's domain, under Google's TLS certificate. What comes back to PrintShop is a token scoped to photos.readonly, expiring in an hour, revocable from your account settings tonight.
Everything about that is good engineering. The password never spreads. The grant is narrow. The blast radius of a PrintShop breach is "some photos were read," not "an attacker has your Google password and you reused it on four other sites."
The step that trips people up
Why does the authorization server hand back a short-lived code in step 4, only for the app to immediately trade it for a token in step 5? Why not just return the token?
Because step 4 travels through the browser, in a URL, where it lands in browser history, in Referer headers, and in the access logs of every proxy in between. Step 5 travels over a direct server-to-server call that includes the app's client_secret — something a browser never possesses.
So the code is deliberately useless on its own. Intercepting it gains you nothing without the secret. That's the entire reason for the two-step dance, and it's why the implicit flow — which skipped it — is now firmly deprecated.
The expensive mistake
Now the part that costs people real money.
An access token is a bearer token. The name is the whole story: whoever bears it, uses it. It carries no signature you check, no statement about who authorized it, and critically, no statement about which application it was issued to.
So what happens when a developer, reasonably, thinks: I have a token that reaches Maya's Google account. I'll call the userinfo endpoint, get back maya@example.com, and start a session for Maya.
That's token substitution, and it's the reason "Log in with OAuth" is a security bug rather than a feature.
The subtlety that makes it survive code review is that the userinfo call succeeds. The response is genuine. Google really is saying "this token reaches Maya's account." The developer tests it, sees maya@example.com come back, and ships.
What they've missed is the difference between two questions that look identical and aren't:
"Whose data does this token reach?" — which is what an access token can tell you.
"Did this person just authenticate, to my application, right now?" — which is what login requires, and which no access token can answer.
An attacker who runs any app you've ever granted access to — a quiz, a photo filter, a Slack integration — is holding a token that reaches your account. If another app treats that as proof of identity, he is you there.
This isn't theoretical. It's the root of the Facebook "Login with" issues of the mid-2010s, it's why the IETF published a dedicated OAuth 2.0 Security Best Current Practice, and it is still, in 2026, in code I get handed to review.
OpenID Connect: the thin layer that fixes it
Here's what I like about OIDC: it didn't try to replace OAuth. It looked at a protocol everyone had already deployed and added the smallest possible thing that makes authentication safe.
Three changes. That's the whole specification's practical surface.
The one that matters is aud. An ID token is a signed JWT whose audience claim names exactly one client ID — yours. Replay a token minted for the quiz app and your signature-and-audience check rejects it in microseconds. The envelope is addressed; the access token never was.
The nonce closes the other door. You generate a random value, send it in the authorization request, and the provider echoes it back inside the signed token. Replaying an older token for the same app fails too, because its nonce won't match the one you just issued.
If you remember one sentence from this post: use OIDC for login, use OAuth for API access, and never use an access token to decide who someone is.
And note the third row of that diagram — the obligations. Verifying the signature but forgetting the audience check is the single most common OIDC implementation bug I run into. It leaves you precisely as exposed as plain OAuth, while feeling secure. Use a certified library. The spec authors wrote the hard parts for you.
SAML: still running the building
If OIDC is OAuth's younger sibling, SAML is the grandparent who still runs the family business, and runs it well.
SAML 2.0 was ratified in 2005 — before the iPhone, before "API" meant REST, in a world where the integration problem wasn't "my mobile app needs your API" but "forty thousand employees at Company A need to use a tool hosted by Company B, and our networks will never touch."
That constraint shaped everything about it.
Look at where the messages go. The library portal and the university IdP never open a connection to each other. Every byte travels through Maya's browser — a redirect out, an auto-submitting HTML form back. The signed XML assertion is the payload; the browser is the courier.
That seems baroque until you remember the requirement: two organisations, two security teams, two firewalls, no shared network, no pre-exchanged runtime secrets beyond a signing certificate. SAML solved genuine federation in a hostile topology, and it solved it well enough that twenty-one years later it still carries most enterprise and university SSO on earth.
Why it isn't dead, and won't be
People have been predicting SAML's retirement since roughly 2015. It keeps not happening, for reasons worth understanding:
The attribute model is richer than people expect. eduPersonAffiliation, eduPersonEntitlement, department codes, licence seats — higher education in particular built deep attribute vocabularies into SAML, and there's no clean OIDC equivalent that institutions have agreed on.
Federations exist as institutions. InCommon in the US, eduGAIN internationally — these are legal and operational trust fabrics with thousands of members, not just protocol choices. You don't migrate those with a sprint.
It already works. The least glamorous reason and the most decisive one.
What SAML is genuinely bad at is anything that isn't a browser. A 4KB base64 XML blob delivered by HTML form POST is hopeless for a native mobile app or a service-to-service call. That's not a flaw so much as a scope boundary — it was designed for browsers in 2005, and that's what it does.
Open the envelope
Abstract comparisons only go so far. Here is what each credential actually contains:
The size row at the bottom explains a lot of architectural history. An ID token at ~800 bytes rides comfortably in an Authorization header. A SAML assertion at ~4KB does not — which is why it arrives by form POST, and why nobody puts SAML in a mobile app.
Choosing, in practice
Strip away the taxonomy and the decision is usually quick:
| You are building | Use | Because |
|---|---|---|
| Login for a web or mobile app | OIDC | Purpose-built for it, and the token is addressed to you |
| An API that third parties call on users' behalf | OAuth 2.0 | This is literally the problem it solves |
| Login and API access | OIDC | You get both tokens from one flow |
| SSO into an enterprise SaaS tool | SAML, usually | It's what the customer's IdP already speaks |
| Higher-ed or government federation | SAML | InCommon, eduGAIN, and the attribute vocabularies |
| Service-to-service, no user present | OAuth client credentials | No human means nothing to authenticate |
Two notes from the field.
You will end up supporting both. Every serious B2B product I've worked on eventually runs OIDC for its own users and SAML for enterprise customers whose IdP speaks nothing else. Plan for that from the start rather than bolting it on during a procurement cycle — I have watched a deal slip a quarter over exactly this.
Let the customer's IdP decide. When an enterprise asks for SSO, the correct question is not "which protocol do you prefer?" It's "what does your identity provider already emit?" You are the one adapting.
The short version
SAML asks who you are, in signed XML, carried by the browser, across organisational boundaries. Old, verbose, deeply entrenched, and very good at the job it was built for.
OAuth 2.0 asks what an application may do on your behalf. It is a valet key. It is not a login, it was never a login, and the access token it issues cannot tell you who anyone is.
OpenID Connect asks who you are using OAuth's own machinery, and closes the gap with one signed, addressed token.
The mistake worth tattooing somewhere: an access token tells you what a bearer may reach. An ID token and a SAML assertion tell you who authenticated, and to whom they said it. Those are different sentences, and every time an engineer treats the first as the second, someone eventually logs in as someone else.
Wiring up SSO and hitting something strange? I have debugged a lot of these — clock skew on assertion validity windows is my personal favourite. Find me on LinkedIn.
Discussion
Loading…