In this article

SAML vs OAuth: A technical guide

Exploring the differences between SAML and OAuth, how they function, and how to choose which one to support.

SAML and OAuth solve two fundamentally different problems. SAML answers the question "Who is this user?" OAuth answers the question "What is this app allowed to do?" Conflating the two is one of the most common mistakes teams make when designing their auth architecture.

This guide breaks down how each protocol works at a technical level, where they overlap, where they don't, and how to decide which one your application needs.

The core distinction: authentication vs. authorization

Authentication is identity verification: proving that a user is who they claim to be. Authorization is access delegation: defining what actions or data a given entity is permitted to access.

SAML is an authentication protocol. It lets a trusted identity provider (IdP) vouch for a user's identity to your application. Your app never sees a password. It receives a cryptographically signed assertion that says "this is alice@acme.com, and she authenticated at 14:32 UTC."

OAuth is an authorization framework. It lets a user grant your application scoped, time-limited access to their resources on another service. Your app never sees the user's credentials for that service. It receives a token that says "this app can read this user's repos until this token expires."

These are different jobs. The confusion arises because both protocols show up in conversations about Single Sign-On, and because OAuth is frequently extended (via OpenID Connect) to handle authentication too.

How SAML works

SAML (Security Assertion Markup Language) uses XML-based messages exchanged between two parties:

  • Identity Provider (IdP): The system that authenticates the user, such as Okta, Azure AD, Google Workspace, or any SAML-compliant IdP.
  • Service Provider (SP): Your application, which trusts the IdP to verify identity.

The SP-initiated flow

This is the most common flow. The user starts at your app.

  1. A user navigates to your application (the SP).
  2. Your app determines the user isn't authenticated and generates a SAML AuthnRequest.
  3. Your app redirects the user's browser to the IdP's SSO endpoint, carrying the AuthnRequest as a query parameter (HTTP-Redirect binding) or in a POST body (HTTP-POST binding).
  4. The IdP authenticates the user via password, MFA, certificate, or an existing session.
  5. The IdP constructs a SAML Response containing one or more Assertion elements. Each assertion includes:
    • A Subject with a NameID identifying the user.
    • Conditions specifying validity constraints (timestamps, audience restriction).
    • An AuthnStatement confirming how and when authentication occurred.
    • Optionally, an AttributeStatement carrying user attributes (email, roles, groups, department).
  6. The IdP signs the response (and optionally the assertion itself) with its private key and redirects the user's browser back to your app's Assertion Consumer Service (ACS) URL via HTTP-POST.
  7. Your app validates the XML signature against the IdP's public certificate, checks the Conditions (audience, timestamps, replay), extracts the identity and attributes, and creates a local session.

The IdP-initiated flow

Here, the user starts at the IdP. They click an app tile in their Okta or Azure AD dashboard. The IdP generates an unsolicited SAML response and POSTs it directly to your ACS URL. This flow skips the AuthnRequest step, which means your app has no request context to correlate against. IdP-initiated SSO is considered less secure because it's more susceptible to replay attacks and cross-site request forgery, but many enterprises still require support for it.

What's inside a SAML assertion

A SAML assertion is a signed XML document. A simplified structure looks like this:

  
<saml:Assertion Version="2.0" ID="_abc123" IssueInstant="2026-03-31T14:32:00Z">
  <saml:Issuer>https://idp.acme.com</saml:Issuer>
  <ds:Signature>...</ds:Signature>
  <saml:Subject>
    <saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress">
      alice@acme.com
    </saml:NameID>
  </saml:Subject>
  <saml:Conditions NotBefore="2026-03-31T14:31:00Z" NotOnOrAfter="2026-03-31T14:37:00Z">
    <saml:AudienceRestriction>
      <saml:Audience>https://yourapp.com</saml:Audience>
    </saml:AudienceRestriction>
  </saml:Conditions>
  <saml:AuthnStatement AuthnInstant="2026-03-31T14:32:00Z">
    <saml:AuthnContext>
      <saml:AuthnContextClassRef>
        urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
      </saml:AuthnContextClassRef>
    </saml:AuthnContext>
  </saml:AuthnStatement>
  <saml:AttributeStatement>
    <saml:Attribute Name="Role">
      <saml:AttributeValue>admin</saml:AttributeValue>
    </saml:Attribute>
  </saml:AttributeStatement>
</saml:Assertion>
  

The XML signature is critical. Without it, any intermediary could forge an assertion. Your SP must validate the signature against the IdP's known certificate and reject any assertion with an invalid or missing signature.

How OAuth works

OAuth 2.0 defines a framework for issuing scoped access tokens. The core actors are:

  • Resource Owner: The user who owns the data.
  • Client: Your application, which wants to access the user's data.
  • Authorization Server: The service that authenticates the user and issues tokens (e.g., GitHub's OAuth server).
  • Resource Server: The API that holds the user's data (e.g., GitHub's API).

The Authorization Code flow

This is the recommended flow for server-side web applications and the most secure standard OAuth flow.

  1. Your app redirects the user's browser to the authorization server's /authorize endpoint with parameters: client_id, redirect_uri, response_type=code, scope, and a state parameter (a random value for CSRF protection).
  2. The authorization server authenticates the user and presents a consent screen showing what your app is requesting access to.
  3. The user approves (or denies) the request.
  4. The authorization server redirects back to your redirect_uri with an authorization code and the state value.
  5. Your app verifies state matches the original value, then exchanges the authorization code for tokens by making a back-channel POST to the /token endpoint, sending the code, client_id, client_secret, and redirect_uri.
  6. The authorization server responds with an access_token (and optionally a refresh_token).
  7. Your app uses the access token in the Authorization: Bearer <token> header to call the resource server's API.

Other grant types

OAuth 2.0 defines several grant types for different application architectures:

  • Authorization Code + PKCE: Adds a code verifier/challenge exchange to protect public clients (SPAs, mobile apps) that can't securely store a client secret. This is now the recommended flow for all client types per current best practices.
  • Client Credentials: For machine-to-machine communication where no user is involved. Your app authenticates directly with its own credentials to get a token.
  • Device Authorization: For input-constrained devices (smart TVs, CLI tools) that can't easily handle browser redirects.

The Implicit grant and Resource Owner Password Credentials grant are deprecated in OAuth 2.1 due to security weaknesses.

Tokens in OAuth

An access token is an opaque or structured string (often a JWT) that represents a granted authorization. It has a scope (what the token permits), an expiration, and an audience (which API it's valid for). Access tokens are short-lived by design, typically lasting minutes to an hour.

A refresh token is a longer-lived credential used to obtain new access tokens without requiring the user to re-authenticate. Refresh tokens must be stored securely server-side and should be rotated on use.

Where the confusion comes from: OAuth and OpenID Connect

OAuth by itself does not tell you who a user is. It tells you that someone authorized your app to access certain resources. The access token isn't meant to carry identity.

OpenID Connect (OIDC) is a thin authentication layer built on top of OAuth 2.0. It adds:

  • An id_token, which is a JWT that contains identity claims (sub, email, name, etc.) about the authenticated user.
  • A /userinfo endpoint for fetching additional profile data.
  • Standardized scopes like openid, profile, and email.

When people say "we use OAuth for login," they almost always mean they're using OIDC. The OAuth framework itself has no concept of identity. This distinction matters architecturally: if you're using raw OAuth without OIDC for authentication, you're relying on implementation-specific behavior that can introduce security holes.

SAML vs. OAuth vs. OIDC: A technical comparison

Dimension SAML 2.0 OAuth 2.0 OIDC (OAuth + identity)
Primary function Authentication (+ some authorization via attributes) Authorization (access delegation) Authentication + authorization
Data format XML JSON JSON / JWT
Token / assertion type Signed XML assertion Access token (opaque or JWT), refresh token ID token (JWT) + access token
Transport Browser redirects (HTTP-POST, HTTP-Redirect) Browser redirects + back-channel HTTP Same as OAuth
Signature model XML Digital Signature on assertions Token signing (JWTs use JWS); TLS for transport JWT signatures (JWS)
Discovery SAML Metadata XML OAuth Authorization Server Metadata OIDC Discovery (.well-known/openid-configuration)
Session management SAML Single Logout (SLO) No native session concept OIDC Session Management, Back-Channel Logout
Best suited for Enterprise workforce SSO API access delegation, third-party integrations Consumer and workforce authentication, SSO

How to choose

Implement SAML when

  • Your customers are enterprises. Large organizations manage employee access through identity providers like Okta, Azure AD, or PingFederate. They expect (and often require) SAML-based SSO before purchasing. If you're selling B2B SaaS, SAML support is a prerequisite for moving upmarket.
  • You need to federate identity across a corporate ecosystem. SAML excels at connecting an organization's central IdP to dozens or hundreds of internal and external applications. The protocol was designed for this exact topology.
  • You're integrating with legacy enterprise systems. Many established enterprise applications only support SAML. If you need to interoperate with these systems, SAML is non-negotiable.

Implement OAuth when

  • Your app needs to access user data on third-party services. Reading a user's Google Calendar events, posting to their Slack workspace, or syncing files from their Dropbox: these are OAuth's sweet spot. You're asking the user to delegate limited access to their data, not proving who they are.
  • You're building an API that third-party developers will integrate with. OAuth provides the standard framework for issuing scoped, revocable tokens that let external apps interact with your API on behalf of your users.
  • You need machine-to-machine authorization. The Client Credentials grant handles service-to-service auth without any user involvement.

Implement OIDC (OAuth + OpenID Connect) when

  • You want "Login with Google/GitHub/Microsoft" in your app. Social login is OIDC in practice. The identity provider authenticates the user and sends your app a signed ID token with their profile.
  • You need a modern, lightweight SSO protocol. OIDC gives you the same single sign-on capability as SAML but with JSON instead of XML, JWTs instead of signed XML blobs, and simpler integration for SPAs and mobile apps.
  • You're building consumer-facing authentication. OIDC's flows were designed with modern app architectures in mind, including single-page apps, native mobile apps, and APIs.

Implement both when

  • You're building a B2B SaaS product. In practice, most B2B applications need SAML for enterprise customers who require workforce SSO through their corporate IdP, and OAuth/OIDC for social login, third-party API integrations, or smaller customers who don't have a SAML IdP.

Common implementation pitfalls

  • Using OAuth for authentication without OIDC. An access token doesn't prove who a user is. Without the id_token and the validation guarantees OIDC provides, you're open to token substitution and confused deputy attacks.
  • Skipping state parameter validation in OAuth. The state parameter prevents CSRF attacks. If your app doesn't generate a random state, bind it to the user's session, and verify it on callback, an attacker can inject their own authorization code into a victim's session.
  • Not validating SAML assertion signatures correctly. XML Signature Wrapping (XSW) attacks exploit parsers that validate a signature on one element but read identity from another. Always use a well-tested SAML library. Do not write your own XML signature validation.
  • Storing OAuth tokens insecurely. Access tokens and refresh tokens are bearer credentials. If they leak through logs, local storage in the browser, or insecure databases, attackers gain the same access your app has. Store tokens server-side, encrypted at rest, and use short-lived access tokens with refresh token rotation.
  • Ignoring SAML Conditions. The NotBefore, NotOnOrAfter, and AudienceRestriction fields exist to prevent replay attacks and cross-service assertion reuse. Skipping these checks undermines the security model entirely.

Summary

SAML and OAuth are complementary, not competing. SAML is an authentication protocol that lets enterprise identity providers vouch for users. OAuth is an authorization framework that lets users grant scoped access to their resources. OIDC extends OAuth to handle authentication.

The right choice depends on what you're building: SAML for enterprise SSO, OAuth for API access delegation, OIDC for modern authentication flows. Most production applications end up implementing more than one.

Further Reading:

This site uses cookies to improve your experience. Please accept the use of cookies on this site. You can review our cookie policy here and our privacy policy here. If you choose to refuse, functionality of this site will be limited.