Skip to main content Skip to sidebar

SAML vs OAuth2 vs OIDC

If you have ever logged into an application using “Sign in with Google” or accessed a corporate dashboard without typing your password twice, you have already used one of these protocols. SAML, OAuth2, and OpenID Connect (OIDC) solve related but different problems. This post breaks down what each protocol does, how it works, and when to pick one over another.

The Core Problem

Modern applications rarely manage usernames and passwords on their own. Instead, they delegate identity and access decisions to a trusted third party. This pattern is called federated identity. The three protocols covered here are the most common ways to implement it.

Before diving in, two terms worth knowing:

  • Authentication (AuthN): proving who you are (e.g., entering a password).
  • Authorization (AuthZ): proving what you are allowed to do (e.g., reading a file).

SAML (Security Assertion Markup Language)

SAML is an XML-based protocol designed in the early 2000s. It is primarily an authentication protocol used in enterprise environments. When your company’s intranet redirects you to a single sign-on (SSO) page, SAML is likely running behind the scenes.

How SAML Works

SAML involves three parties:

  • Principal: the user trying to log in
  • Service Provider (SP): the application the user wants to access
  • Identity Provider (IdP): the system that authenticates the user (e.g., Okta, Azure AD)
sequenceDiagram
    participant User
    participant SP as Service Provider
    participant IdP as Identity Provider

    User->>SP: 1. Access application
    SP->>User: 2. Redirect to IdP with SAML Request
    User->>IdP: 3. Follow redirect
    IdP->>User: 4. Show login page
    User->>IdP: 5. Submit credentials
    IdP->>User: 6. Redirect back with SAML Response (XML assertion)
    User->>SP: 7. Forward SAML Response
    SP->>SP: 8. Validate assertion and create session
    SP->>User: 9. Grant access

The SAML Response contains an assertion, an XML document signed by the IdP. The assertion carries information about the user (name, email, roles) and a digital signature the SP can verify.

SAML Characteristics

  • Format: XML
  • Transport: HTTP redirects and POST requests via the browser
  • Primary use case: enterprise SSO, one login for many internal applications
  • Token type: SAML Assertion (XML document)
  • Strengths: mature, widely adopted in enterprise, strong security model with XML signatures
  • Weaknesses: verbose XML payloads, complex to implement, not mobile-friendly

SSO Initiation: SP vs IdP

SAML supports two ways to start the authentication process depending on where the user begins.

SP-Initiated SSO is the most common flow. The user navigates to the application (Service Provider) first. The SP detects the user is not authenticated and redirects them to the IdP for login. After successful authentication, the IdP sends the SAML assertion back to the SP.

sequenceDiagram
    participant User
    participant SP as Service Provider
    participant IdP as Identity Provider

    User->>SP: 1. Access application
    SP->>User: 2. Not authenticated, redirect to IdP
    User->>IdP: 3. Follow redirect (with AuthnRequest)
    IdP->>IdP: 4. Authenticate user
    IdP->>User: 5. Redirect back with SAML Response
    User->>SP: 6. Forward SAML Response
    SP->>User: 7. Grant access

IdP-Initiated SSO starts at the Identity Provider. The user logs into the IdP portal (e.g., an Okta dashboard) and clicks on an application tile. The IdP generates a SAML assertion and sends it directly to the SP without the SP requesting it first.

sequenceDiagram
    participant User
    participant IdP as Identity Provider
    participant SP as Service Provider

    User->>IdP: 1. Log into IdP portal
    User->>IdP: 2. Click application tile
    IdP->>IdP: 3. Generate SAML assertion
    IdP->>User: 4. Redirect to SP with SAML Response
    User->>SP: 5. Forward SAML Response
    SP->>User: 6. Grant access
SP-InitiatedIdP-Initiated
Starting pointApplication (SP)Identity Provider portal
AuthnRequestYes, SP sends request to IdPNo, IdP sends assertion unsolicited
SecurityMore secure (SP validates request context)Less secure (no request to correlate with response)
Common useDirect application URLs, bookmarksEnterprise dashboards, app portals

SP-Initiated is generally preferred because the SP can include a unique request ID in the AuthnRequest and verify it matches the InResponseTo field in the SAML Response. This prevents replay attacks. IdP-Initiated SSO skips this step, making it more vulnerable to unsolicited response attacks if not carefully implemented.

OAuth2

OAuth2 is an authorization framework published in 2012. It was designed to let a user grant a third-party application limited access to their resources without sharing their password.

A classic example: you allow a photo-printing service to read your Google Photos. The printing service never sees your Google password. It only receives a short-lived access token that grants read access to your photos.

How OAuth2 Works

OAuth2 defines four roles:

  • Resource Owner: the user who owns the data
  • Client: the application requesting access
  • Authorization Server: issues tokens after the user consents
  • Resource Server: hosts the protected data (e.g., Google Photos API)
sequenceDiagram
    participant User as Resource Owner
    participant Client
    participant AuthServer as Authorization Server
    participant API as Resource Server

    User->>Client: 1. Use application
    Client->>AuthServer: 2. Redirect to authorization endpoint
    AuthServer->>User: 3. Show consent screen
    User->>AuthServer: 4. Approve access
    AuthServer->>Client: 5. Return authorization code
    Client->>AuthServer: 6. Exchange code for access token
    AuthServer->>Client: 7. Return access token
    Client->>API: 8. Request data with access token
    API->>Client: 9. Return protected resource

This is the Authorization Code flow, the most common and secure grant type. OAuth2 also defines other flows (Implicit, Client Credentials, Device Code), but the Authorization Code flow covers the majority of use cases.

PKCE (Proof Key for Code Exchange)

The Authorization Code flow has a vulnerability: if an attacker intercepts the authorization code during the redirect (e.g., on a mobile device via a malicious app registered for the same custom URI scheme), they can exchange it for an access token. PKCE (pronounced “pixy”, defined in RFC 7636) solves this.

Before starting the flow, the client generates a random code verifier and derives a code challenge from it using SHA-256. The client sends the code challenge with the initial authorization request. When exchanging the authorization code for a token, the client sends the original code verifier. The authorization server hashes it and compares with the stored challenge. An attacker who intercepted the code cannot complete the exchange without the verifier.

sequenceDiagram
    participant Client
    participant AuthServer as Authorization Server

    Client->>Client: 1. Generate code_verifier (random string)
    Client->>Client: 2. Derive code_challenge = SHA256(code_verifier)
    Client->>AuthServer: 3. Authorization request + code_challenge
    AuthServer->>Client: 4. Return authorization code
    Client->>AuthServer: 5. Token request + code_verifier
    AuthServer->>AuthServer: 6. Verify SHA256(code_verifier) == code_challenge
    AuthServer->>Client: 7. Return access token

PKCE was originally designed for mobile and single-page applications that cannot securely store a client secret. Since 2021, the OAuth 2.1 draft recommends PKCE for all clients, including confidential (server-side) ones, as a defense-in-depth measure.

OAuth2 Characteristics

  • Format: JSON
  • Transport: HTTP redirects, JSON REST calls
  • Primary use case: delegated authorization, granting third-party apps limited access to user data
  • Token type: Access Token (opaque string or JWT)
  • Strengths: simple, widely supported, great for APIs and mobile apps
  • Weaknesses: OAuth2 by itself does not define authentication. It tells you the user allowed something, not who the user is

The Identity Gap in OAuth2

OAuth2 has no standard way to retrieve user identity. Different providers invented their own endpoints (e.g., /userinfo), leading to incompatible implementations. This gap is exactly what OIDC was created to fill.

OpenID Connect (OIDC)

OpenID Connect is a thin authentication layer built on top of OAuth2. Published in 2014, it adds a standardized way to verify the user’s identity using an ID Token.

Think of it this way: OAuth2 gives you a key to a room. OIDC also hands you an ID card that says who you are.

How OIDC Works

OIDC reuses the OAuth2 Authorization Code flow and adds an ID Token (a JWT) to the response.

sequenceDiagram
    participant User
    participant Client as Application
    participant Provider as OIDC Provider

    User->>Client: 1. Click "Sign in with Google"
    Client->>Provider: 2. Redirect with scope=openid
    Provider->>User: 3. Show login / consent screen
    User->>Provider: 4. Authenticate and approve
    Provider->>Client: 5. Return authorization code
    Client->>Provider: 6. Exchange code for tokens
    Provider->>Client: 7. Return access token + ID token (JWT)
    Client->>Client: 8. Validate ID token (verify signature, claims)
    Client->>User: 9. User is logged in

The ID Token is a JSON Web Token containing standard claims:

ClaimMeaning
subUnique identifier for the user
issWho issued the token (the provider)
audWho the token is intended for (your app)
expWhen the token expires
iatWhen the token was issued
emailUser’s email address (optional)
nameUser’s display name (optional)

The client validates the token by checking the signature against the provider’s public keys (published at a well-known endpoint), verifying the aud matches its own client ID, and ensuring the token has not expired.

OIDC Characteristics

  • Format: JSON / JWT
  • Transport: HTTP redirects, JSON REST calls (same as OAuth2)
  • Primary use case: user authentication, “Sign in with X” buttons
  • Token type: ID Token (JWT) + Access Token
  • Strengths: standardized identity on top of OAuth2, simple for developers, great for web and mobile
  • Weaknesses: less mature in legacy enterprise environments where SAML dominates

Discovery: openid-configuration

OIDC defines a standard discovery mechanism. Every OIDC provider publishes a JSON document at a well-known URL:

https://{issuer}/.well-known/openid-configuration

The client fetches this document once and learns everything it needs to interact with the provider: where to send authorization requests, where to exchange codes for tokens, where to find public keys for signature verification, and what features the provider supports. No manual configuration required.

A typical response looks like this:

{
  "issuer": "https://auth.example.com",
  "authorization_endpoint": "https://auth.example.com/authorize",
  "token_endpoint": "https://auth.example.com/oauth/token",
  "userinfo_endpoint": "https://auth.example.com/userinfo",
  "jwks_uri": "https://auth.example.com/.well-known/jwks.json",
  "scopes_supported": ["openid", "profile", "email"],
  "response_types_supported": ["code", "token", "id_token"],
  "grant_types_supported": [
    "authorization_code",
    "refresh_token",
    "client_credentials"
  ],
  "subject_types_supported": ["public"],
  "id_token_signing_alg_values_supported": ["RS256"],
  "token_endpoint_auth_methods_supported": [
    "client_secret_basic",
    "client_secret_post"
  ],
  "code_challenge_methods_supported": ["S256"]
}

Key fields:

FieldPurpose
issuerBase URL of the provider, must match iss in tokens
authorization_endpointWhere to redirect the user for login
token_endpointWhere to exchange the authorization code for tokens
userinfo_endpointWhere to fetch additional user profile claims
jwks_uriWhere to download public keys for token signature verification
scopes_supportedWhat scopes the provider accepts
response_types_supportedWhat OAuth2/OIDC flows are available
code_challenge_methods_supportedWhat PKCE methods are supported

This is one of the key advantages OIDC has over both SAML and plain OAuth2. SAML uses XML-based metadata documents that require manual exchange between SP and IdP. OAuth2 has no discovery mechanism at all, so endpoints must be configured manually per provider. OIDC discovery makes integration straightforward: point your client library at the issuer URL, and it configures itself automatically.

Side-by-Side Comparison

SAMLOAuth2OIDC
PurposeAuthentication (SSO)AuthorizationAuthentication + Authorization
Year200520122014
SpecificationOASIS StandardRFC 6749OpenID Foundation
Data formatXMLJSONJSON / JWT
Token typeSAML AssertionAccess TokenID Token + Access Token
Token sizeLarge (kilobytes)Small (opaque) or medium (JWT)Medium (JWT)
SignatureXML Digital SignatureNot defined (TLS)JWS (JSON Web Signature)
TransportHTTP Redirect, POSTHTTP Redirect, RESTHTTP Redirect, REST
EncryptionXML Encryption (optional)TLS onlyJWE (optional)
DiscoveryMetadata XMLNot defined.well-known/openid-configuration
Built forEnterprise / web browsersAPIs / third-party accessWeb, mobile, APIs
Mobile supportPoorGoodGood
SPA supportNot practicalYes (with PKCE)Yes (with PKCE)
Machine-to-machineNot designed for itYes (Client Credentials)Yes (Client Credentials)
User identityYes (assertion attributes)No (not standardized)Yes (/userinfo + ID Token)
Session managementSingle Logout (SLO)Not definedFront/Back-Channel Logout
Implementation complexityHighMediumMedium
Library ecosystemSmaller, enterprise-focusedLargeLarge (extends OAuth2 libraries)

Example Payloads

SAML Assertion

A SAML assertion is an XML document. Even a minimal one is verbose compared to JSON-based tokens:

<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
    ID="_a]b1c2d3e4f5" Version="2.0"
    IssueInstant="2026-02-11T12:00:00Z">
  <saml:Issuer>https://idp.example.com</saml:Issuer>
  <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
    <!-- XML Digital Signature (truncated) -->
  </ds:Signature>
  <saml:Subject>
    <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">
      user@example.com
    </saml:NameID>
    <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
      <saml:SubjectConfirmationData
          InResponseTo="_request123"
          Recipient="https://app.example.com/saml/acs"
          NotOnOrAfter="2026-02-11T12:05:00Z"/>
    </saml:SubjectConfirmation>
  </saml:Subject>
  <saml:Conditions NotBefore="2026-02-11T12:00:00Z"
                   NotOnOrAfter="2026-02-11T12:05:00Z">
    <saml:AudienceRestriction>
      <saml:Audience>https://app.example.com</saml:Audience>
    </saml:AudienceRestriction>
  </saml:Conditions>
  <saml:AuthnStatement AuthnInstant="2026-02-11T12:00: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="email">
      <saml:AttributeValue>user@example.com</saml:AttributeValue>
    </saml:Attribute>
    <saml:Attribute Name="displayName">
      <saml:AttributeValue>John Doe</saml:AttributeValue>
    </saml:Attribute>
    <saml:Attribute Name="role">
      <saml:AttributeValue>admin</saml:AttributeValue>
    </saml:Attribute>
  </saml:AttributeStatement>
</saml:Assertion>

OAuth2 Access Token

An OAuth2 access token can be an opaque string or a JWT. The authorization server response looks like this:

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...JWT_PAYLOAD...signature",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4"
}

When the access token is a JWT, its decoded payload contains authorization scopes but no standardized identity claims:

{
  "iss": "https://auth.example.com",
  "sub": "user-123",
  "aud": "https://api.example.com",
  "scope": "read write",
  "exp": 1739282800,
  "iat": 1739279200
}

The client uses the token in API requests via the Authorization header:

GET /api/v1/photos HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...

OIDC ID Token

An OIDC token response includes both an access token and an ID token. The ID token is always a JWT:

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "id_token": "eyJhbGciOiJSUzI1NiIs...JWT_PAYLOAD...signature",
  "refresh_token": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4"
}

Decoded ID token payload:

{
  "iss": "https://auth.example.com",
  "sub": "user-123",
  "aud": "my-app-client-id",
  "exp": 1739282800,
  "iat": 1739279200,
  "email": "john@example.com",
  "name": "John Doe"
}

Key differences from the OAuth2 access token:

  • aud is the client ID (not the API URL), because the ID token is meant for the application, not the resource server
  • Identity claims (email, name) are standardized by the OIDC specification

Can They Work Together?

Yes. In practice, many organizations use SAML for internal enterprise SSO and OIDC/OAuth2 for customer-facing applications and API access. An Identity Provider like Okta or Azure AD can speak both protocols simultaneously, acting as a SAML IdP for legacy enterprise apps and an OIDC Provider for modern web and mobile clients.

Summary

  • SAML is the enterprise veteran: XML-based, battle-tested, and ideal for browser-based SSO in corporate environments
  • OAuth2 is the authorization framework: it controls what apps can access, not who the user is
  • OIDC is OAuth2 plus identity: it adds a standardized authentication layer, making it the go-to choice for modern login flows

If you are starting a new project and need user login, start with OIDC. If you are integrating with an existing enterprise IdP, support SAML. If you only need API authorization without user identity, OAuth2 alone is sufficient.