Skip to content
pwnsy
web-securityintermediate#saml#oauth#oidc#sso#identity

SAML vs OAuth vs OIDC: Three Identity Protocols Explained

How SAML, OAuth 2.0, and OpenID Connect differ, what each one actually does, and which to reach for when you build SSO or authorize an API.

Three acronyms turn up in almost every conversation about single sign-on, and they are constantly used as if they were interchangeable. They are not. SAML, OAuth 2.0, and OpenID Connect were designed at different times, by different communities, to answer different questions. Once you can say which question each one answers, the choice of what to build with stops being guesswork.

Here is the shortest honest summary. SAML does enterprise single sign-on with signed XML. OAuth 2.0 hands one app limited permission to act on your behalf against another app's API. OpenID Connect uses OAuth 2.0 machinery to also tell an app who you are. The rest of this guide unpacks each, then shows where they overlap and how to pick.

Authentication versus authorization

Every one of these protocols lives on one side or the other of a single distinction, so it is worth nailing down first.

Authentication answers "who is this user". It is the login moment: proving that the person at the keyboard is the account holder.

Authorization answers "what is this user, or this app, allowed to do". It is about permission and scope, and it happens after identity is established or delegated.

SAML and OIDC are primarily about authentication and federated login. OAuth 2.0 is purely about authorization. The most common security failure in this space comes from treating OAuth 2.0 as if it also did authentication, which it was never designed to do.

The confusion has a real cause. All three protocols hand tokens around over browser redirects, all three involve a central provider and a relying application, and in a login-with-a-social-account flow the same click can trigger both an authorization grant and an identity assertion. From the outside the flows look nearly identical. The difference lives in what the token means and what the application is entitled to conclude from holding it. Keep asking one question, "does this token tell me who the user is, or only what an app may do," and the three protocols stop blurring together.

SAML

Security Assertion Markup Language is the elder of the three, standardised by OASIS and widely deployed since the mid-2000s. It was built for the enterprise web: an employee logs in once to a central identity provider, then reaches many separate applications without logging in again.

The core object is the assertion, an XML document, signed by the identity provider (IdP), that states facts about the user: their identifier, when they authenticated, and attributes such as group membership. The application receiving it is the service provider (SP).

A typical browser flow runs like this. The user hits the service provider, which has no session, so it builds an authentication request and redirects the browser to the identity provider. The identity provider authenticates the user, then returns a signed assertion back to the service provider, usually posted through the browser. The service provider validates the signature and creates a local session.

SAML's strengths are its maturity, its rich attribute model, and near-universal support in corporate identity products. Its weaknesses are the XML tooling, the complexity of XML signature validation (a historically fertile source of bugs), and a poor fit for mobile apps and single-page frontends.

SAML also comes in two initiation styles worth knowing. In SP-initiated flow the user starts at the application, which bounces them to the identity provider, the pattern described above and the safer default. In IdP-initiated flow the identity provider sends an unsolicited assertion straight to the application, for example from a corporate app portal. IdP-initiated flow is convenient but harder to secure, because the application receives an assertion it never asked for, which removes a natural check against replay. Prefer SP-initiated where you can.

OAuth 2.0

OAuth 2.0, defined in RFC 6749, is a delegated authorization framework. Its problem statement is different from SAML's. You want a third-party app to access some of your data in another service, for example letting a scheduling tool read your calendar, without handing that app your password.

OAuth 2.0 defines four roles:

RoleWhat it is
Resource ownerThe user who owns the data
ClientThe app that wants access
Authorization serverIssues tokens after the user consents
Resource serverThe API holding the data, which accepts tokens

The output is an access token, a string the client presents to the resource server to make API calls within a granted scope. The user approves that scope during the flow, and the client never sees the user's password.

The critical thing to internalise is what an access token is not. It is not proof of who the user is. It is a bearer credential that says "the holder of this string may do these things until it expires". OAuth 2.0 says nothing about authentication, session length, or user identity. Apps that decode an access token and treat its contents as a verified login are building on sand.

OAuth 2.0 also defines several grant types for different client situations, and choosing the right one is part of using the framework safely. The authorization code grant, paired with PKCE, is the modern default for web, mobile, and single-page clients, because the sensitive code-for-token exchange happens away from the browser with a proof only the real client holds. The client credentials grant covers machine-to-machine access where no user is present, so the application authenticates as itself. The older implicit and resource-owner password grants are discouraged now, the first because it exposed tokens in the browser and the second because it asks the user to hand their password to the client, which is the exact practice OAuth set out to avoid. Matching the grant to the client keeps tokens out of places they can leak.

The access token is not a login

An OAuth 2.0 access token proves authorization, not identity. Do not use it to log a user in, do not trust its contents as who the user is, and do not treat receiving one as proof that authentication happened. That gap is exactly what OpenID Connect exists to fill.

OpenID Connect

OpenID Connect (OIDC) is a thin identity layer built directly on top of OAuth 2.0. It reuses the same flows and endpoints, then adds the piece OAuth 2.0 left out: a standard, verifiable statement of who the user is.

That statement is the ID token, a JSON Web Token (a signed JWT) issued by the OpenID provider. It contains claims such as the subject identifier (sub), the issuer (iss), the audience (aud), and issue and expiry times. Because it is signed, the client can verify it came from the trusted provider and was meant for this client. For deeper mechanics of the token format, see What Is a JWT.

OIDC also standardises supporting pieces that OAuth 2.0 left to each vendor: a UserInfo endpoint for fetching profile claims, a discovery document at a well-known URL that publishes the provider's endpoints and keys, and defined scopes like openid, profile, and email.

So the division of labour is clean. In an OIDC flow you receive two tokens. The ID token tells your app who logged in. The access token lets your app call APIs on the user's behalf. Keep their jobs separate and most identity bugs never appear.

A worked example: logging in with OIDC

Trace a single sign-in to see how the pieces move. A user clicks "Sign in" on your web application, which is an OIDC relying party.

  1. Your app builds an authorization request and redirects the browser to the provider's authorization endpoint. The request names your client, the scopes you want (starting with openid), a redirect URI the provider has on file for you, and two anti-abuse values: a random state and a random nonce. On public clients you also send a PKCE code challenge derived from a secret you keep.
  2. The provider authenticates the user through whatever means it owns, a password, a passkey, a second factor, and shows a consent screen for the requested scopes if needed. Your application never sees the credentials.
  3. The provider redirects the browser back to your redirect URI with a short-lived authorization code and the same state you sent. Your app checks that state matches what it stored, which ties the response to the request it actually made and blocks a cross-site request forgery on the callback.
  4. Your app's server exchanges the code at the provider's token endpoint, presenting the PKCE code verifier. Because this call is server to server and the code is one-time and short-lived, an attacker who glimpsed the code in a redirect cannot replay it without the verifier.
  5. The provider returns an ID token and, if you asked for API access, an access token. Your app validates the ID token: it checks the signature against the provider's published keys, that the issuer matches the expected provider, that the audience is your client, that the token has not expired, and that the nonce equals the one you sent. Only after all of those pass does your app treat the user as logged in and create its own session.

Every check in step five has a job. Skipping the signature check lets a forged token through. Skipping the audience check lets a token minted for a different application be replayed at yours. Skipping the nonce check reopens a replay window the nonce was added to close. The ID token is only trustworthy because you verified it, and an unverified ID token is worth nothing.

The nonce and the state do different jobs

It is easy to conflate the two random values in an OIDC flow. The state parameter protects the redirect itself, tying the callback to the request your app started so an attacker cannot graft their own response onto a victim's session. The nonce protects the ID token, binding it to this specific authentication so a previously issued token cannot be replayed. Send both, store both, and check both. Dropping either one removes a distinct protection.

How they compare

DimensionSAMLOAuth 2.0OIDC
Primary jobFederated SSO / authenticationDelegated authorizationAuthentication on OAuth 2.0
Data formatXML assertionsOpaque or JWT access tokensJWT ID tokens
Era and originMid-2000s, OASIS2012, IETF2014, OpenID Foundation
Best fitEnterprise web SSOAPI access delegationModern web, mobile, SPA login
AnswersWho is the userWhat may the app doWho is the user
TransportBrowser redirect + back channelHTTPS redirects and token endpointSame as OAuth 2.0

Notice that SAML and OIDC answer the same question. Both do federated login. The difference is format and era: SAML speaks XML and dominates legacy enterprise, OIDC speaks JSON and JWTs and suits modern clients. OAuth 2.0 answers a genuinely different question, which is why pairing it with OIDC is so common.

Which one to use

The choice is usually decided for you by what you must integrate with, so start there.

  1. You need users to log in to your app. Reach for OIDC. It gives you a verifiable ID token, a discovery document, and broad library support across web, mobile, and single-page apps.
  2. You need your app to call another service's API on a user's behalf. That is OAuth 2.0, using the authorization code flow with PKCE. Grant the narrowest scope that works.
  3. You must federate with an enterprise identity provider that speaks SAML. Many corporate IdPs still lead with SAML, so integrate over SAML even if you would prefer OIDC. This is the most common reason to still choose SAML for new work.
  4. You are building both login and API access. Use OIDC and OAuth 2.0 together. The same authorization server issues an ID token for identity and an access token for API calls in one flow.
Follow the provider, then the flow

Pick the protocol your identity provider speaks well, then pick the flow. For OAuth 2.0 and OIDC on browser and mobile clients, that flow is authorization code with PKCE. Avoid the implicit flow, which is deprecated for good security reasons.

For the threat side of getting these flows wrong, our Exploit Intelligence dashboard tracks live vulnerabilities in identity and SSO components, so you can see which SAML, OAuth, and OIDC implementation flaws are actually under active exploitation rather than merely theoretical.

Tokens and assertions side by side

Each protocol carries identity or permission in a different container, and the containers have different rules.

Token or assertionProtocolSaysWho should read itFormat
SAML assertionSAMLWho the user is, plus attributesThe service providerSigned XML
Access tokenOAuth 2.0What the holder may doThe resource server (API)Opaque or JWT
ID tokenOIDCWho the user isThe client that requested loginSigned JWT
Refresh tokenOAuth 2.0 / OIDCAuthority to get new access tokensThe authorization server onlyOpaque

The audience rule is the thread running through the table. A SAML assertion is meant for a specific service provider. An access token is meant for a resource server and your client should treat it as opaque, never parsing it to learn about the user. An ID token is meant for your client, and a resource server should not accept it as an API credential. A refresh token is for the authorization server alone. Most token-confusion bugs are a token being read by a party it was never addressed to.

Validation and detection

Whether you are building these flows or reviewing them, a handful of concrete checks separate a sound integration from a fragile one.

  • Verify every signature against the provider's current keys. For OIDC and JWT-based tokens, fetch the signing keys from the discovery document's JWKS endpoint and check the token's key identifier. For SAML, validate the XML signature and confirm it covers the assertion you are trusting, not some unsigned wrapper around it.
  • Pin the issuer and the audience. Reject any token whose issuer is not your expected provider or whose audience is not your application. A token valid for a different tenant or client must not authenticate a user at yours.
  • Enforce expiry and, for SAML, the assertion time window. Check exp on JWTs and the NotBefore and NotOnOrAfter conditions on SAML assertions, with only a small clock-skew allowance.
  • Bind the response to the request. Confirm the state on the OAuth or OIDC callback, and the nonce inside the ID token, match values your application generated and stored.
  • Watch for signature-stripping and algorithm confusion. A JWT arriving with alg set to none, or an RS256 token that a naive verifier checks as HS256 using the public key as an HMAC secret, are classic bypasses. Reject unexpected algorithms outright.
  • Log token audience and issuer mismatches. A spike in tokens presented to the wrong audience, or SAML assertions failing signature validation, is a signal worth alerting on, because it can indicate an attacker probing a confused-deputy path.
XML signature validation is its own hazard

SAML rides on XML digital signatures, and validating them correctly is famously error-prone. Signature-wrapping attacks move the signed element while slipping an attacker-controlled assertion into a place the application reads but the signature does not cover. If you consume SAML, use a mature, maintained library, validate that the signature covers exactly the assertion you act on, and avoid hand-rolling the XML processing. This is the same parser-trust problem that makes XML a recurring source of authentication bypasses.

How the three protocols evolved

The timeline explains why three overlapping standards coexist. SAML came out of the early-2000s enterprise web, when large organizations needed employees to reach many internal and vendor applications with one corporate login, and XML was the interchange format of that world. It solved federation well for server-rendered web apps and remains deeply embedded in corporate identity products.

OAuth arrived later to solve a different itch: the rise of third-party apps that wanted access to a user's data in another service without collecting the user's password. OAuth 1.0 addressed it with a signing scheme that was awkward to implement. OAuth 2.0 replaced that with a framework built around bearer tokens and TLS, trading the signing complexity for simpler flows that depend on the transport for protection. By design it stopped at authorization and said nothing about identity.

That silence is exactly what a wave of ad hoc "log in with" schemes tried to fill by misusing OAuth as if an access token proved identity, which produced real vulnerabilities. OpenID Connect was standardized to close the gap properly, layering a defined ID token, a UserInfo endpoint, and discovery on top of OAuth 2.0 so that authentication had a real, verifiable answer rather than a token being pressed into a job it was never meant for. The result is the current division: OIDC for who you are, OAuth 2.0 for what an app may do, and SAML holding the enterprise ground it has occupied for two decades.

Frequently asked questions

Is OIDC just OAuth 2.0 with extra steps? OIDC reuses OAuth 2.0's flows and endpoints, and it adds the piece OAuth deliberately omitted: a signed, verifiable statement of the user's identity in the form of an ID token, plus standard endpoints for discovery and profile claims. It is an identity layer on top of an authorization framework, so calling it OAuth with a defined authentication result is fair.

Can I use a SAML assertion to authorize API calls? That is not its design. SAML assertions are for establishing a federated session at a service provider. Delegated API authorization is OAuth 2.0's job, using access tokens and scopes. Some gateways bridge the two, and the clean model keeps assertions for login and access tokens for API access.

Why is the implicit flow discouraged? The implicit flow returned tokens directly in the browser redirect, exposing them in URLs and history and offering no code-exchange step to protect. The authorization code flow with PKCE is the current recommendation for browser and mobile clients because the sensitive exchange happens against the token endpoint with a proof only the legitimate client holds.

Do I still need SAML for a new application? Only if you must federate with an identity provider that leads with SAML, which many enterprise IdPs still do. For a greenfield choice with no such constraint, OIDC covers login and OAuth 2.0 covers API access with better support across mobile and single-page clients.

What actually goes wrong when someone treats an access token as a login? An access token is a bearer credential for an API and carries no reliable statement of who the user is. Code that decodes one and trusts its contents as identity can be fooled by a token minted for another application or another user, which is the confused-deputy pattern OIDC's audience-scoped ID token was created to prevent.

Which protocol handles single logout? SAML has a defined single logout profile, though it is notoriously tricky to implement consistently across service providers. OIDC has its own session management and logout specifications. In practice, coordinated logout across many applications is one of the harder problems in federated identity regardless of protocol.

The mistakes worth avoiding

Three errors account for most of the trouble. First, using an OAuth 2.0 access token as a login credential, which OIDC exists specifically to prevent. Second, skipping validation: an ID token you do not verify the signature, issuer, and audience on is worthless, and a SAML assertion whose XML signature you do not properly check has been the root of real authentication bypasses. Third, over-scoping: granting an app more permission than the task needs, so that a token leak becomes a much larger breach.

Get the question right first. Are you proving who the user is, or granting an app permission to act? That single question routes you to the correct protocol nearly every time, and the rest is implementation detail.

Sources & further reading

Sharetwitterlinkedin

Related guides