Skip to content
Published on

SSO Fundamentals — SAML vs OAuth 2.0 vs OIDC, What to Use and When

Authors

Introduction

The moment your internal systems exceed ten, and the moment SaaS adoption gets serious, every organization hits the same question: "How many passwords are users supposed to remember?" Single Sign-On (SSO) is the industry-standard answer to that question, and the protocols that implement the answer are SAML 2.0, OAuth 2.0, and OpenID Connect (OIDC).

There are clear reasons to revisit this topic in 2026.

  1. OAuth 2.1 becoming the de facto standard — It is still an IETF draft (draft-ietf-oauth-v2-1), but mandatory PKCE and the removal of Implicit/ROPC have already hardened into industry best practice. It consolidates RFC 6749, RFC 7636, and RFC 9700 (the OAuth 2.0 Security BCP) into one document.
  2. Passkeys becoming the default — As WebAuthn/FIDO2-based passkeys take over as the primary authentication factor, it has become more important to separate "how an SSO protocol conveys an authentication result" from "how the user actually authenticates at the IdP". Keycloak 26.6 integrated passkeys into the login form with conditional/modal UI.
  3. AI agent identity — With the explosion of non-human identities, the OAuth family of protocols that deals with delegation has grown in importance. The experimental OAuth Client ID Metadata Document (CIMD) support in Keycloak 26.6 targets MCP (Model Context Protocol) authorization server scenarios.

This article walks through how SSO works, the history and role differences of the three protocols, and the decision criteria for "what to use when" — all in one place.

How SSO Works — Centralizing Trust

The essence of SSO can be summarized in one sentence: strip authentication out of each application, delegate it to a central trusted authority, and have applications verify only the "certificate of proof" issued by that authority.

The Cast: IdP, SP, RP

TermFull nameCampRole
IdPIdentity ProviderSAML/commonActually authenticates users and issues proof (assertion/token)
SPService ProviderSAMLThe application that trusts the IdP proof and serves the user
RPRelying PartyOIDCThe OIDC term for the SP — the party relying on the IdP (OP)
OPOpenID ProviderOIDCThe OIDC term for the IdP
ASAuthorization ServerOAuthThe authorization server that issues access tokens
RSResource ServerOAuthThe server that validates access tokens and serves APIs

The vocabulary differs per camp, but the structure is identical: the "authenticating side" (IdP/OP/AS) and the "relying side" (SP/RP/RS) establish a trust relationship in advance, and the user only has to log in once at the authenticating side.

The Generalized SSO Flow

 [User browser]            [SP / RP application]           [IdP / OP]
        |                            |                         |
        |--- 1. Access app --------->|                         |
        |                            |                         |
        |<-- 2. "Auth required, go to IdP" (redirect) --|      |
        |                            |                         |
        |--- 3. Auth request (AuthnRequest / authorize) ------>|
        |                            |                         |
        |<-- 4. Login UI (password, passkey, MFA...) ----------|
        |--- 5. Submit credentials --------------------------->|
        |                            |                         |
        |<-- 6. Issue proof + redirect back to app ------------|
        |        (SAML Response / authorization code)          |
        |                            |                         |
        |--- 7. Deliver proof ------>|                         |
        |                            |-- 8. Verify (signature/ |
        |                            |        code exchange)   |
        |<-- 9. App session created, service provided --|      |
        |                            |                         |
   On the next app: the IdP already holds an SSO session,
   so steps 4-5 (login UI) are skipped and step 6 happens
   immediately --> this is SSO

The last lines are the key. Because the IdP maintains an SSO session (usually a cookie) on its own domain, from the second application onward the proof is issued instantly without a login screen. From the perspective of the user, "I logged in once and every app just opens."

How Trust Is Established

For SSO to work, the SP and IdP must exchange the following ahead of time.

  • SAML: metadata XML exchange — entityID, the X.509 certificate for signature verification, endpoint URLs
  • OIDC: client registration — client_id, client_secret (or asymmetric keys), a redirect URI allowlist. IdP-side information is auto-discovered via the Discovery document

If this trust configuration is broken (expired certificate, redirect URI mismatch, etc.), SSO does not work. The majority of incidents you meet in operations originate here.

Sessions vs Tokens — Where Does State Live?

To understand SSO, you first need to distinguish the two ways of maintaining "logged-in state".

Session-based (stateful)

[Browser] --(Cookie: JSESSIONID=abc123)--> [Server]
                                              |
                                              v
                                    [Session store (memory/Redis)]
                                    abc123 -> userId=42, roles=[admin]
  • The server keeps state in a session store, and only an opaque session ID is handed to the browser as a cookie.
  • Pros: the server can invalidate a session instantly (forced logout); no sensitive data in the cookie.
  • Cons: horizontal scaling requires a shared session store; hard to cross domain boundaries (cookies are bound to a domain).

Token-based (stateless)

[Client] --(Authorization: Bearer eyJhbGciOi...)--> [API server]
                                                          |
                                                          v
                                            Verifies the signature only
                                            (no store lookup needed)
  • A signed, self-contained token (usually a JWT) carries user info and permissions, and the client carries it around.
  • Pros: stateless servers, freely crosses domain/service boundaries, fits microservices.
  • Cons: an issued token is hard to revoke instantly before expiry — mitigated with short lifetimes plus refresh tokens.

SSO Uses Both

A common misconception is that "tokens replaced sessions". In real SSO architectures, the two coexist.

LocationState mechanismRole
IdP domainSession (cookie)The SSO session — the basis for skipping re-login
Web app (server-rendered)Its own app session (cookie)A local session established after verifying the IdP proof
SPA/mobile to APIToken (Bearer)API call authorization

The SSO session at the IdP is a cookie, what the app receives is a token, and a traditional web app creates its own session again after token verification. Managing these three distinct lifetimes is the heart of SSO operations (and the reason logout is hard).

The History of the Three Protocols — Why Do Three Exist?

2002        2005          2006~2010        2012          2014           2025~
 |            |               |              |             |               |
SAML 1.0   SAML 2.0      OpenID 1/2      OAuth 2.0      OIDC 1.0      OAuth 2.1
(OASIS)    (OASIS,       OAuth 1.0a      (RFC 6749)    (auth layer    (draft,
            enterprise    (web 2.0 API    (authorization on top of     PKCE required,
            web SSO)       delegation)     framework)    OAuth 2.0)    Implicit removed)

SAML 2.0 (2005) — The Original Enterprise Web SSO

SAML (Security Assertion Markup Language) is an XML-based protocol standardized by OASIS. The era matters: in 2005 there were no smartphones and no SPAs, and what enterprises cared about was "let employees log in once across multiple web applications". So SAML was designed around browser redirects and HTML form POSTs, with XML Signature guaranteeing integrity. It remains the most widely used option for B2B SSO integrations with enterprise SaaS such as Workday, Salesforce, and AWS IAM Identity Center.

OAuth 2.0 (2012) — An Authorization Framework

OAuth started from an entirely different problem: "I want a third-party app to read my Google contacts, but I do not want to give that app my Google password." In other words, the goal is delegated authorization without password sharing. OAuth 2.0, standardized as RFC 6749, is a framework that issues a limited-permission key called an access token — and it does not define a standard way of telling who the user is. That is the single most misunderstood point.

OIDC (2014) — An Authentication Layer on Top of OAuth 2.0

When demand for "login with OAuth" exploded, every company invented its own non-standard variant (Facebook Connect and friends). OpenID Connect 1.0 cleaned up that chaos: on top of the OAuth 2.0 flow it added the ID Token (a JWT) as the authentication artifact, plus the UserInfo endpoint, Discovery, and dynamic client registration. One-line summary: OIDC = OAuth 2.0 + standardized authentication + JWT + automatic metadata discovery.

OAuth 2.1 (in progress) — Consolidating 14 Years of Security Lessons

OAuth 2.1 is not about new features; it is a consolidation. It merges RFC 6749 + RFC 7636 (PKCE) + RFC 9700 (Security BCP) and removes dangerous patterns.

  • PKCE mandatory for all clients (including confidential clients)
  • Implicit Grant (response_type=token) deleted
  • Resource Owner Password Credentials (ROPC) grant deleted
  • Bearer tokens in URL query strings prohibited
  • Refresh tokens must be sender-constrained or rotated

If you are designing a system in 2026, the right answer is "use OAuth 2.0 but follow the 2.1 constraints as written".

Authentication vs Authorization — The Most Important Distinction

QuestionConceptProtocol in charge
Who are you?Authentication (AuthN)SAML, OIDC
What are you allowed to do?Authorization (AuthZ)OAuth 2.0

By analogy:

  • OIDC/SAML = issuing an ID card. "This person is Youngju Kim, and our IdP vouches that he authenticated with a passkey five minutes ago."
  • OAuth 2.0 = issuing a hotel key card. "This card opens room 305 and the gym. The card does not tell you who is holding it."

This is where a notorious anti-pattern comes from: using an access token as proof of authentication. An access token only proves "permission to call an API"; it carries no guarantee that "the person sending this request is the owner of the token". Attacks really happened where an access token issued to one app was stolen and replayed for login elsewhere, and the OIDC ID Token (a JWT whose aud claim pins the audience) is precisely the fix. Login decisions must be made with the ID Token.

The Actual Flows of the Three Protocols

SAML 2.0 — SP-initiated Web SSO

[Browser]                [SP: app.example.com]        [IdP: idp.corp.com]
    |                            |                            |
    |--- GET /dashboard -------->|                            |
    |<-- 302 Redirect ----------- (SAMLRequest=base64+deflate)|
    |                            |                            |
    |--- GET /sso?SAMLRequest=...&RelayState=... ------------>|
    |<-- Login screen (skipped if SSO session exists) --------|
    |--- Submit credentials ---------------------------------->|
    |                            |                            |
    |<-- 200 + auto-submitting HTML form (SAMLResponse) ------|
    |--- POST /acs (SAMLResponse, RelayState) -->|            |
    |                            |-- Verify XML signature,    |
    |                            |   parse Assertion, check   |
    |<-- Set-Cookie: app session-|   conditions (time, audience)

Characteristics: everything is front-channel via the browser (POST binding), the artifact is an XML Assertion, and it can work even without direct SP-to-IdP connectivity.

OIDC — Authorization Code Flow + PKCE

[Browser/app]            [RP: app.example.com]        [OP: idp.corp.com]
    |                            |                            |
    |--- GET /login ------------>|                            |
    |                            |-- generate code_verifier,  |
    |                            |   compute code_challenge   |
    |<-- 302 /authorize?response_type=code&client_id=...      |
    |        &scope=openid+profile&state=...&nonce=...        |
    |        &code_challenge=...&code_challenge_method=S256   |
    |                            |                            |
    |--- GET /authorize --------------------------------------->
    |<-- Login screen (skipped if SSO session exists) ---------|
    |--- Authentication done ----------------------------------->
    |<-- 302 redirect_uri?code=AUTH_CODE&state=... ------------|
    |--- GET /callback?code=... ->|                            |
    |                            |--- POST /token ----------->|
    |                            |    (code + code_verifier   |
    |                            |     + client auth)         |
    |                            |<-- id_token, access_token, |
    |                            |    refresh_token ----------|
    |                            |-- verify id_token signature|
    |<-- Login complete ---------|   and claims               |

Characteristics: tokens travel over the back channel (direct server-to-server TLS), the artifact is a JWT, PKCE defends against code interception, and Discovery automates configuration.

OAuth 2.0 Alone — Third-party API Delegation

The flow is the same as OIDC, but scope contains no openid and no id_token is issued. The only artifact is an access token, and its purpose is "calling resource server APIs", not "identifying the user".

Comparison Table — At a Glance

AspectSAML 2.0OAuth 2.0OIDC 1.0
Standardized byOASIS, 2005IETF RFC 6749, 2012OpenID Foundation, 2014
EssenceAuthentication + attribute transferDelegated authorization frameworkAuthentication layer on OAuth 2.0
Data formatXML (Assertion)Undefined (usually JWT/opaque)JWT (ID Token) + JSON
IntegrityXML SignatureTLS + token signing (implementation-defined)JWS signature (keys via JWKS)
ChannelMostly front-channel (Redirect/POST)Back-channel centricBack-channel centric
MetadataManual metadata XML exchangeNone (RFC 8414 fills the gap)Discovery document, automatic
Mobile/SPA fitPoorGood (PKCE)Good (PKCE)
API authorizationUnsuitableIts main jobThe access token part is plain OAuth
Logout standardSingle Logout (SLO)NoneRP-Initiated/Back-Channel Logout
Main habitatEnterprise B2B SSOAPI delegation, service-to-serviceConsumer/workforce login, modern apps

Protocol Selection Decision Tree

START
 |
 +-- Q1. Do you need user login (authentication)?
 |     |
 |     +-- No (pure API delegation, service-to-service calls)
 |     |     --> OAuth 2.0 (Client Credentials for machine-to-machine,
 |     |         follow OAuth 2.1 constraints, apply RFC 9700)
 |     |
 |     +-- Yes
 |          |
 |          +-- Q2. Are you integrating your own app,
 |          |       or an external customer IdP?
 |          |
 |          +-- B2B integration with an external customer IdP
 |          |     |
 |          |     +-- Customer supports OIDC? --> OIDC (preferred)
 |          |     +-- Customer supports SAML only? --> SAML 2.0
 |          |         (enterprise reality: many shops are still SAML-only)
 |          |
 |          +-- Your own app (own IdP / social login)
 |                |
 |                +-- Web, SPA, mobile, or desktop — any of them
 |                      --> OIDC Authorization Code + PKCE
 |                          (the 2026 default for new builds)
 |
 +-- Q3. Do you also need API calls after login?
       --> Log in with OIDC + use the access token obtained in the
           same flow for OAuth 2.0 resource access
           (they are complements, not alternatives)

In summary:

  • OIDC is the default for new builds. Practically the only reason to adopt SAML anew is "the other side supports only SAML".
  • OAuth 2.0 alone is for pure authorization without login (service accounts, API delegation).
  • SAML is not legacy — it remains first-string as "the lingua franca of enterprise B2B". New feature investment, however, happens on the OIDC side.

Enterprise vs B2C — Selection Criteria

CriterionEnterprise (workforce/B2B)B2C (consumer)
Primary protocolSAML or OIDC (match the peer IdP)OIDC
IdPCorporate IdP (Entra ID, Okta, Keycloak, etc.)Own OP + social login
User lifecycleHR integration, SCIM (RFC 7644) provisioningSelf sign-up, email/phone verification
Auth factorsCorporate policy (forced MFA, device trust)Passkeys first, social delegation
Session policyShort, strict idle timeoutLong, friction minimized
LogoutSLO frequently requiredSingle-app logout often suffices
ComplianceSOC 2, ISO 27001, industry regulationPrivacy laws, GDPR

A part frequently forgotten in enterprise SSO is provisioning. SSO only solves "login"; account creation, entitlement assignment, and deactivating leavers are separate problems, and SCIM is the standard there. Most incidents of "a former employee can still log in to a SaaS" stem not from SSO but from missing provisioning.

The 2026 Perspective — OAuth 2.1, Passkeys, and Beyond

Apply the OAuth 2.1 Constraints Now

There is no need to wait for OAuth 2.1 to become an RFC. Things to do today:

Checklist (OAuth 2.1 alignment)
[ ] PKCE (S256) for all clients — including confidential clients
[ ] Migrate SPAs still on Implicit Flow to Code + PKCE
[ ] Eradicate ROPC (password grant) — no exceptions, not even test code
[ ] Remove any code passing access tokens in URL query strings
[ ] Apply rotation to refresh tokens of public clients
[ ] Register redirect_uri with exact match only

How Passkeys Relate to SSO

Passkeys are not a competitor to SSO protocols — they are the primary authentication factor inside the IdP. The structure layers like this:

[Apps] --(OIDC/SAML: convey auth result)--> [IdP] --(WebAuthn: actual user auth)--> [passkey]

When the IdP adopts passkeys, every connected app gains phishing-resistant authentication without a single line of code changed. That is the real value of an SSO architecture. The login-form passkeys integration (conditional UI) in Keycloak 26.6 is a good example.

Other Currents Worth Watching

  • FAPI 2.0: the financial-grade security profile is Final, and Keycloak 26.6 supports the FAPI 2.0 Security Profile and Message Signing. If you operate in a high-security domain, consider adopting the FAPI 2.0 profile.
  • AI agents and delegation: an agent calling APIs on behalf of a user is exactly the OAuth delegation model. Specs such as Token Exchange (RFC 8693) and CIMD are evolving rapidly in this space.
  • Verifiable Credentials: decentralized identity with an issuer-holder-verifier model is discussed as a long-term alternative to SSO, but as of 2026 the enterprise mainstream remains OIDC/SAML.

Hands-on Example — Registering OIDC and SAML Clients Side by Side on One IdP

The fastest way to internalize the concepts is to register clients for both protocols side by side on the same IdP (here, Keycloak 26.6).

First the OIDC side. For a Spring Boot app, specifying just the issuer in application.yml lets Discovery resolve every other endpoint.

spring:
  security:
    oauth2:
      client:
        registration:
          keycloak:
            client-id: web-dashboard
            client-secret: CHANGE_ME
            authorization-grant-type: authorization_code
            scope: openid,profile,email
            redirect-uri: "https://app.example.com/login/oauth2/code/keycloak"
        provider:
          keycloak:
            issuer-uri: https://idp.corp.com/realms/prod

Next, create both clients with the Keycloak admin CLI (kcadm).

# OIDC client (new web app)
/opt/keycloak/bin/kcadm.sh create clients -r prod \
  -s clientId=web-dashboard \
  -s protocol=openid-connect \
  -s 'redirectUris=["https://app.example.com/login/oauth2/code/keycloak"]' \
  -s publicClient=false \
  -s standardFlowEnabled=true \
  -s 'attributes={"pkce.code.challenge.method":"S256"}'

# SAML client (legacy B2B SaaS)
/opt/keycloak/bin/kcadm.sh create clients -r prod \
  -s 'clientId=https://legacy.example.com/saml/metadata' \
  -s protocol=saml \
  -s 'redirectUris=["https://legacy.example.com/saml/acs"]' \
  -s 'attributes={"saml.signature.algorithm":"RSA_SHA256","saml_assertion_consumer_url_post":"https://legacy.example.com/saml/acs"}'

Once both apps are wired up, something interesting happens. Same user, same IdP SSO session — yet one app receives a JWT (the ID Token) and the other receives an XML Assertion. If the user logs in to the OIDC app first and then visits the SAML app, an Assertion is issued instantly with no login screen. It is an experiment that lets you feel firsthand that the IdP acts as a protocol translation hub, and that the SSO session lives one layer below the protocols.

Operational and Security Best Practices

  1. Keep token lifetimes short; renew with refresh tokens — access tokens 5 to 15 minutes, refresh tokens with rotation. Use the ID Token only for verification right after login; do not store it.
  2. Always use state and nonce — state defends against CSRF; nonce defends against ID Token replay. Verify, do not assume, that your library does this.
  3. Synchronize clocks — both SAML Assertions and JWTs carry time conditions. A single server with drifted NTP creates intermittent login failures. Configure a clock skew allowance of 60 to 120 seconds during validation.
  4. Manage key/certificate lifecycles — manage SAML certificate expiry and OIDC signing key rotation with calendars and automation. Set the JWKS cache TTL appropriately (minutes to hours) so key rotation is zero-downtime.
  5. Design logout from day one — write down as a requirement "how far should logout reach" (app session only? the IdP SSO session? every app?). It is the hardest feature to bolt on later.
  6. IdP redundancy and incident response — if the IdP dies, login dies for every app. Operate the IdP at your highest availability tier and use features such as the zero-downtime rolling patch updates in Keycloak 26.6.

Anti-patterns You Will Meet

Anti-patternWhat is wrongFix
Login decisions from an access tokenNo audience check; token substitution attacksDecide login with the ID Token
New use of Implicit FlowTokens exposed in URL fragmentCode + PKCE
ROPC for first-party loginTrains users for phishing; blocks MFA/passkeysSystem browser + Code + PKCE
JWT in localStorageOne XSS leaks all tokenshttpOnly cookie session or BFF pattern
Skipping/partial SAML signature checksAssertion forgery, Signature WrappingVetted library + sign both Response and Assertion
Wildcard redirect_uri registrationCode theft via open redirectExact match only
Refresh tokens that live foreverIndefinite session if stolenRotation + reuse detection
SSO without SCIMLeaver accounts lingerSCIM provisioning/deprovisioning

Closing

Compressing the relationship of the three protocols one last time:

  • OAuth 2.0 is "how to delegate permissions". It is not an authentication protocol.
  • OIDC layers a standard "who are you" on top of OAuth 2.0 and is the default for new builds in 2026.
  • SAML 2.0 remains on active duty as the lingua franca of enterprise B2B SSO, but compatibility is virtually the only reason to choose it anew.
  • OAuth 2.1 is not a new protocol — it is "the codification of security hygiene that has become common sense". Start following it today.

The next article digs into the internals of SAML 2.0 (Assertions, Bindings, Metadata), and the one after that into the internals of OIDC (Code Flow, Discovery, token validation) at the code level.

References