Guide · Backend
OAuth vs OIDC vs SAML: Login vs Access, Explained Clearly
23 min readUpdated By Saket Bhatnagar
Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.
In short
- OAuth vs OIDC vs SAML comes down to one question: are you proving who someone is, or letting an app call an API for them? An API is the set of web addresses apps call for data. OAuth 2.0 grants access. OIDC and SAML log people in.
- OAuth 2.0 gives an app an access token, a pass (usually short-lived) that says what the app may do. OAuth on its own defines no standard way to learn who the user is.
- OpenID Connect (OIDC) is a thin layer on top of OAuth 2.0. It adds an ID token: a signed note in JSON, a common text data format, saying who logged in and for which app.
- SAML 2.0, a standard from 2005, does the same login job with signed XML (an older tag-based text format) carried by the browser. Many company login systems still use it for single sign-on, one staff login for many apps.
- Beginners can stop after the section on which one to use. Engineers then get an ID token check tested on PyJWT 2.15.1, a common Python library. It includes an OIDC audience rule, about which apps a token is meant for, that PyJWT does not enforce for you. They also get SAML pitfalls and OAuth security rules from the IETF, the body that writes internet standards.
What goes wrong when login and access get mixed up?
Two things go wrong: people who left a company keep access, and a token meant for another app gets accepted as a login to yours. Both come from treating login and access as one problem. Getting OAuth vs OIDC vs SAML straight closes the second hole and shrinks the first.
Picture a common setup. A team sells a web CRM (customer relationship management app, where sales staff track their clients) to other businesses. Every customer's staff got a separate CRM password. The setup is made up, but the problem is real.
A salesperson leaves customer A. The company's IT admin closes her company account the same day but forgets her CRM password. Three weeks later, that old account is still exporting client lists.
So customer A asks for single sign-on (SSO): staff log in once with the company account, and many apps trust that login. Disabling one company account should then end CRM logins too.
Soon the team has three requests that sound alike but are not:
- Customer A's login system speaks SAML. Its admin sends an XML file describing the system and asks the CRM to accept logins from it.
- Customer B's login system speaks OpenID Connect. Its IT team registers the CRM and gives it a client ID, the name their login system knows it by. They ask for a redirect address, the page users return to after logging in.
- The team's own mobile app needs to call the CRM's API without keeping passwords on the phone.
Someone suggests one answer for all three: just use OAuth. That is half right, and the wrong half can let another app log in as your users. The rest of this guide sorts the three requests out.
Which words do you need before comparing OAuth, OIDC and SAML?
You need five words: authentication, authorization, identity provider, token and claim. Each gets a plain meaning and an example from the CRM.
Authentication. Authentication means proving who you are. Example: a salesperson types her company password and a code from her phone, and the company's login system confirms it is really her.
Authorization. Authorization means deciding what you may do or open. Example: she may read her own team's client list, but not the billing screens.
Identity provider. An identity provider (IdP) is the system that holds user accounts and checks passwords. The app that trusts it is the service provider in SAML and the relying party in OIDC. Example: customer A's login system is the IdP, and the CRM trusts it.
Token. A token is a piece of data one system issues so another system can trust it later, without asking for the password again. Many tokens are JWTs (JSON Web Tokens): small JSON documents with a digital signature attached.
Claim. A claim is one fact inside a token, such as a user ID, an email address or an expiry time. SAML calls its signed bundle of facts an assertion.
OAuth vs OIDC vs SAML: what is the actual difference?
OAuth 2.0 lets an app use an API on a user's behalf, while OIDC and SAML tell an app who the user is. OIDC is built on OAuth and uses JSON. SAML is older, separate from OAuth and uses XML.
A hotel makes the split easy to see. Here is the mapping, part by part:
- Reception checking your ID card is the identity provider authenticating you.
- The room key card is an OAuth access token. It opens room 304 and the gym until checkout, but the gym door never learns your name.
- A signed guest slip naming you, the hotel and the one restaurant it is meant for is an OIDC ID token.
- A printed wristband that you carry across the road to a partner spa is a SAML assertion. You, walking it over, play the part of the browser.
Whoever holds a key card gets in, which is why tokens like this are called bearer tokens. The card proves access, not identity.
Where the picture breaks: a hotel can cancel a key card at the desk at once. An API that checks a token only by its signature cannot know it was cancelled, so the token works until it expires. That is one reason access tokens are kept short-lived.
What is OAuth 2.0, and why is it not a login protocol?
OAuth 2.0 is a standard for giving an app limited access to an API without sharing a password. It answers what this app may do, not who the user is.
RFC 6749, published in October 2012 by the IETF (the body that writes internet standards), names four roles. Here they are for the CRM's mobile app.
- Resource owner: the person who owns the data. Here, a salesperson.
- Client: the app asking for access. Here, the CRM mobile app on her phone.
- Authorization server: the system that issues tokens after the user agrees. Here, the CRM's login service.
- Resource server: the API that holds the data and checks tokens. Here, the CRM API.
The client receives an access token. RFC 6749 calls it a string representing an authorization issued to the client, usually opaque to the client. Opaque means the app does not read it. It just attaches the token to every API call.
Each access token is limited by scopes, named permissions such as contacts.read. The CRM's tokens expire after 15 minutes (a made-up setting). A refresh token lets the app get a new access token without asking her to log in again.
Why an access token is not proof of login
RFC 6749 says the authorization server must first verify the identity of the resource owner. It also says how it does so is beyond the scope of the specification. So a plain access token carries no standard promise about who logged in, or for which app.
Pause and predict: suppose the CRM logs in anyone whose access token the provider still accepts. What could a different app do with that rule?
Here is the answer. A quiz app also lets staff sign in through customer B's identity provider, so it ends up holding an access token for a salesperson. Its owner replays that token at the CRM, the provider says it is valid, and the CRM logs him in as her.
Nothing in a plain access token promises it was meant for the CRM. That missing audience check is the gap OpenID Connect closes.
How the mobile app gets its tokens
The mobile app uses the authorization code flow, the flow RFC 9700 steers apps towards. Follow the arrows and notice one thing: the password only ever goes to the login service, never to the app.
OAuth 2.0 authorization code flow in the CRM mobile app
Drag to exploreThe app never sees the password. It gets a one-time code through the browser, then swaps it for tokens directly with the login service, proving it started the login.
Diagram connections:
- Open the login page to Log in and approve access
- Log in and approve access to Return a one-time code
- Return a one-time code to Swap code for tokens
- Swap code for tokens to Issue access token
- Issue access token to Call API with the token
- Call API with the token to Send back the client list
- Open the login page: Mobile app → Login service, with PKCE hash
- Log in and approve access: Salesperson → Login service
- Return a one-time code: Login service → Mobile app, via browser
- Swap code for tokens: Mobile app → Login service, with PKCE secret
- Issue access token: Login service → Mobile app, plus refresh token
- Call API with the token: Mobile app → CRM API
- Send back the client list: CRM API → Mobile app, if the scope allows
Why swap a code instead of receiving tokens straight away? The code travels through the browser, where it can leak, so the code alone must be useless.
A mobile app cannot keep a secret, because anyone can unpack it. So it uses PKCE (Proof Key for Code Exchange, said pixy), defined in RFC 7636 in September 2015. The app makes a random secret called the code verifier, 43 to 128 characters long.
With the S256 method, the app sends only a SHA-256 hash of the verifier at the start. A hash is a one-way fingerprint. At the swap it reveals the verifier itself, and the login service checks that the two match.
RFC 9700, the IETF's January 2025 security advice for OAuth, says public clients such as mobile apps must use PKCE. For confidential clients, server apps that can keep a secret, it recommends PKCE too.
You can check the hashing step yourself. RFC 7636 Appendix B prints a worked example, and these four lines of Python 3 reproduce it.
import base64, hashlib
verifier = b"dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
digest = hashlib.sha256(verifier).digest()
print(base64.urlsafe_b64encode(digest).rstrip(b"=").decode())Expected output: E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM, the exact code challenge printed in the RFC. Any other string means the encoding is wrong, for example padding left on, or standard base64 used instead of the URL-safe alphabet.
What does OpenID Connect add on top of OAuth 2.0?
OpenID Connect adds login to OAuth 2.0. Alongside the access token, the app also gets an ID token: a signed JWT that says who logged in, at which provider, for which app and until when.
OIDC Core 1.0, from the OpenID Foundation, calls itself a simple identity layer on top of the OAuth 2.0 protocol. With the code flow from the diagram above, the app adds the openid scope, which OIDC requires. The token reply then carries an ID token too.
These are the claims that matter, with what the CRM sees for one salesperson at customer B:
- iss (issuer): who made the token, written as an https address. Here, customer B's identity provider.
- sub (subject): an ID for the user that OIDC Core calls locally unique and never reassigned within the issuer. So the CRM keys each user by iss plus sub, not by email.
- aud (audience): which app the token is for. It must contain the CRM's client ID, or the CRM rejects the token.
- exp and iat: when the token expires, and when it was issued, both in seconds since 1 January 1970 (UTC).
- nonce: a random value the CRM sent when login started. OIDC Core passes it back unmodified inside the ID token, which ties the token to this one login and helps stop replays.
The aud claim stops the quiz app trick. A token issued to the quiz app names the quiz app as its audience, so the CRM refuses it.
OIDC also defines a UserInfo endpoint, an address that returns profile details when the app presents its access token. So the two tokens split the work. The ID token says who the user is, and the access token says what the app may fetch.
How does SAML single sign-on work?
SAML single sign-on works by passing a signed XML document, called an assertion, from the identity provider to the app through the user's browser. The app checks the signature and starts its own session.
SAML stands for Security Assertion Markup Language. Version 2.0 became a standard of OASIS, a body that writes open standards, on 15 March 2005. The OASIS technical overview describes it as an XML-based framework for describing and exchanging security information between online business partners.
In SAML terms, customer A's login system is the identity provider and the CRM is the service provider. SAML calls the ways its messages travel bindings. HTTP Redirect puts a message inside the web address, and HTTP POST carries it in a self-submitting HTML form.
With these two bindings, the CRM and the identity provider never talk directly during login. The browser carries every message, just as the guest carries the wristband. Watch the browser in each step below.
SAML login started from the CRM
Drag to exploreService-provider-started SAML login with the Redirect and POST bindings. The browser carries each message, so the CRM and the IdP never talk directly.
Diagram connections:
- Open the CRM to Send a login request
- Send a login request to Log in at the company
- Log in at the company to Build signed assertion
- Build signed assertion to Post assertion to the CRM
- Post assertion to the CRM to Verify, then start session
- Open the CRM: Salesperson's browser → CRM
- Send a login request: CRM → Customer A's IdP, by browser redirect
- Log in at the company: Salesperson → Customer A's IdP
- Build signed assertion: IdP → Browser, inside a self-posting form
- Post assertion to the CRM: Browser → CRM
- Verify, then start session: CRM → Salesperson's browser
The OASIS overview lists three kinds of statement an assertion can carry:
- Authentication statement: how and when the user logged in. Here, by password and phone code at 9.02 in the morning.
- Attribute statement: facts about the user. Here, her employee number and her department, Sales.
- Authorization decision statement: whether the user may perform a given action on a given resource.
The overview calls this app-started flow the most common way to begin. There is also IdP-initiated login, where the user starts at the company portal and clicks a CRM tile. It is convenient, but the CRM then receives an assertion it never asked for, with no request to match it against.
Setting up SAML means swapping metadata: an XML file with each side's addresses and signing certificate. Customer A's admin uploads the IdP's file to the CRM, and the team sends the CRM's file back. Certificates expire, so both sides note the renewal date.
Which one should you use for login and single sign-on?
Use OIDC for login in new apps, and also support SAML if you sell to companies whose login systems speak only SAML. Use OAuth access tokens, never as logins, to protect your own APIs.
| OAuth 2.0 | OpenID Connect | SAML 2.0 | |
|---|---|---|---|
| Main job | Let an app call an API for a user | Log a user in | Log a user in, mostly staff across company apps |
| Question it answers | What may this app do | Who is this user | Who is this user |
| What the app receives | Access token, often a refresh token | ID token plus access token | Signed XML assertion |
| Data format | Left open by RFC 6749 | ID token is a JWT, which is JSON | XML |
| Who writes the standard | IETF, RFC 6749, October 2012 | OpenID Foundation, Core 1.0, errata set 2, December 2023 | OASIS, version 2.0, March 2005 |
| Fit for mobile apps and APIs | Good | Good | Awkward: its common web profile assumes a browser |
| The CRM's use | Mobile app calling the CRM API | Login for customer B | Login for customer A |
Here is the same choice as a decision. Start at the top box and follow the answers for each of the three requests.
Picking a standard for each request
Drag to exploreLogin and access are separate questions. Both login paths end in the same CRM session, so the rest of the app never cares which standard was used.
Diagram connections:
- A new request to Do we need to log users in?
- Do we need to log users in? to OAuth 2.0 access tokens (No, only API calls)
- Do we need to log users in? to Does their IdP speak OIDC? (Yes)
- Does their IdP speak OIDC? to OpenID Connect (Yes)
- Does their IdP speak OIDC? to SAML 2.0 (No, SAML only)
- OpenID Connect to One CRM session
- SAML 2.0 to One CRM session
- A new request: A customer's login or the team's mobile app
- Do we need to log users in?: Login, not just API access
- OAuth 2.0 access tokens: Code flow with PKCE, short-lived tokens
- Does their IdP speak OIDC?: Ask the customer's IT team first
- OpenID Connect: Customer B
- SAML 2.0: Customer A
- One CRM session: Same roles and 8-hour limit either way
Beginners can stop here: you know which standard answers which question and why. The rest is for engineers building it: an ID token check in code, one app serving SAML and OIDC customers, and the mistakes RFC 9700 warns about.
How do you check an OIDC ID token safely in Python?
Use a maintained JWT library, allow only your provider's signing algorithm, require the claims you rely on, and check audience, issuer and nonce. The function below does this with PyJWT 2.15.1, the latest release as of 19 October 2026.
JWKS_URL is the address where the provider publishes its public signing keys, as a JWKS (JSON Web Key Set). ISSUER and CLIENT_ID come from customer B's setup.
import jwt # PyJWT, tested on 2.15.1
jwks = jwt.PyJWKClient(JWKS_URL)
def verify_id_token(id_token, expected_nonce):
key = jwks.get_signing_key_from_jwt(id_token)
claims = jwt.decode(
id_token,
key.key,
algorithms=["RS256"], # only the method your IdP uses
audience=CLIENT_ID,
issuer=ISSUER,
options={"require": ["exp", "iat", "sub", "nonce"]},
)
if claims["aud"] not in (CLIENT_ID, [CLIENT_ID]):
raise ValueError("token also names other audiences")
if claims["nonce"] != expected_nonce:
raise ValueError("nonce mismatch: possible replay")
return claims["iss"], claims["sub"] # stable key, not emailWhat just happened? The algorithms list accepts only RS256, so a forged token that claims the alg value none, meaning no signature, is refused. The require list makes PyJWT refuse a token missing exp, iat, sub or nonce.
The audience and issuer arguments reject tokens issued to another app or by another provider. The nonce comparison catches an ID token from a different login. The extra audience line is the surprising part.
The OIDC audience rule PyJWT does not enforce
PyJWT is a general JWT library. In version 2.15.1, passing audience=CLIENT_ID succeeds if any one entry in a list-valued aud matches. We tested it: a token whose aud named both the CRM and the quiz app was accepted.
OIDC Core says to reject an ID token that contains additional audiences the client does not trust. The function's extra line refuses any token that names anyone besides the CRM. If your provider adds audiences you do trust, allow those by name instead.
PyJWT's strict_aud option is stricter still: it also refuses an aud written as a one-item list. OIDC Core allows aud to be a list even with one audience, so strict_aud can refuse valid ID tokens. That is why the function checks the list itself. This is the 2.15.1 behaviour, read in its source and tested, as of 19 October 2026; pin your version.
One more library detail: PyJWT checks exp only when the token has one. Without exp in the require list, a token with no expiry passes. The default leeway, the clock drift it forgives, is zero seconds.
How to check your own login code
Before any customer goes live, send your login code bad tokens and confirm each is refused. On PyJWT 2.15.1, using the function above, you should see these exception names:
- Expired one second ago: ExpiredSignatureError.
- Audience names another app: InvalidAudienceError.
- Signed by a different key under the same key ID: InvalidSignatureError.
- Header claims alg none, with a real key ID from the provider's JWKS and no signature: InvalidAlgorithmError. With no key ID at all, PyJWKClientError comes first.
- Audience lists the CRM and the quiz app: the function's own ValueError, token also names other audiences.
For SAML, do the same with an expired assertion, an assertion for another service provider and one with an edited name. Your SAML library should refuse all three.
How do you support SAML and OIDC customers in one app?
Keep one config entry per customer that names its standard, and turn either kind of login into the same internal session. The login code reads the entry and runs the matching flow.
tenants:
customer-a:
protocol: saml
idp_metadata_file: customer-a-idp.xml
user_id_attribute: employeeNumber
allow_idp_initiated: false
customer-b:
protocol: oidc
issuer: https://login.customer-b.example
client_id: crm-web
scopes: [openid, profile, email]
user_id_claim: sub
session:
max_hours: 8
idle_minutes: 30Three lines matter. The user_id_attribute line picks a stable SAML field as the user's key, doing the job sub does in OIDC. Setting allow_idp_initiated to false means the CRM accepts only assertions it asked for. The max_hours line caps every session at one working day.
Supporting two standards adds work, so some teams put an identity broker in front of the app. A broker speaks SAML and OIDC to customers, then hands the app plain OIDC. It keeps the app simple, at the cost of one more system to run.
Single sign-on fixes the original leaver problem, but only partly. Disabling a salesperson at customer A blocks her next login at once. A CRM session that is already open keeps working until it ends, which is why sessions here last one day at most.
Some customers want removal within minutes. For them, SCIM (System for Cross-domain Identity Management, RFC 7644 from September 2015) lets the identity provider push account changes, such as a disabled user, straight to the app.
What security mistakes do teams make with OAuth, OIDC and SAML?
Most real problems come from skipped checks and old shortcuts, not from broken standards. RFC 9700 (January 2025) collects the IETF's current advice for OAuth. Here are the common mistakes, each with its fix.
- Treating an access token as a login. Fix: log users in only from a checked ID token or SAML assertion.
- Using the implicit flow, an older shortcut that returned tokens straight in the browser address. RFC 9700 says clients should not use it, with one narrow exception. Fix: the code flow with PKCE.
- Using the password grant, where the app collects the user's password itself. RFC 9700 says it must not be used.
- Loose redirect addresses. If the login service accepts any address that merely starts like yours, codes can reach an attacker. RFC 9700 requires the login service to match registered redirect addresses exactly. The one exception is the port number in a localhost address (the device's own address) used by a desktop or phone app.
- Tokens that work everywhere. RFC 9700 says access tokens should be limited to one resource server, or a small set, so a leaked token opens less.
- Long-lived refresh tokens on phones. RFC 9700 says a public client's refresh tokens must be sender-constrained, bound to a key only that app holds, or rotated, replaced on every use.
- Skipping SAML checks. Check the signature, the audience, the time window, and that the assertion answers a request you really sent.
- Keying users by email. An address can change or be handed to a new person. Key users by iss plus sub in OIDC, or a fixed employee field in SAML.
OAuth 2.1, an IETF draft that folds much of this advice into one document, is still an Internet-Draft as of 19 October 2026. Its latest revision, 16, is dated 3 September 2026. Build on RFC 6749 plus RFC 9700 today.
How do you explain OAuth vs OIDC vs SAML in a system design interview?
Explain it as two jobs, access and identity, then place each standard on one job. Justify every choice by the kind of client and the kind of customer.
Split the two questions
Say that authentication proves who someone is, and authorization decides what they may do. OAuth answers the second question, while OIDC and SAML answer the first.
Draw the roles
Draw the user, the app, the identity provider and the API. Mark which box issues tokens and which box checks them.
Pick per client
For web and mobile login, choose OIDC with the code flow and PKCE. For calls to your API, use OAuth access tokens. For enterprise customers on SAML, support SAML.
Name the checks
For ID tokens, check signature, issuer, audience, expiry and nonce. For SAML assertions, check signature, audience, time window and that it answers your request.
Cover logout and leavers
Explain session length, refresh token revocation, and how a disabled employee loses access, including SCIM for fast removal. Leavers are the reason customers asked for SSO, so close that loop.
Mention the broker option
If you serve many business customers, say you would turn SAML and OIDC logins into one internal session, perhaps through an identity broker.
Questions people ask
- Is OAuth authentication or authorization?
- OAuth 2.0 is for authorization: it gives an app limited access to an API. RFC 6749 leaves out how the app learns who the user is. OpenID Connect adds that through the ID token, so use OIDC when you need login.
- Is OpenID Connect the same as OAuth?
- No, but it is built on OAuth. OIDC uses the OAuth flows and adds the openid scope, the ID token and the UserInfo endpoint. An OIDC provider is also an OAuth authorization server, but not every OAuth server supports OIDC.
- Is SAML outdated?
- SAML is older, but it is far from gone. Many company login systems still use it for staff single sign-on, so business software often has to support it. For new apps, especially mobile apps and APIs, OIDC is usually simpler to build and run.
- What is the difference between an ID token and an access token?
- An ID token tells your app who logged in, and your app reads it. An access token tells an API what your app may do, and your app should treat it as sealed. Never use an access token as proof of login, and never send an ID token to an API in place of an access token.
- What is PKCE and do I need it?
- PKCE is an add-on to the OAuth code flow that makes a stolen authorization code useless. The app proves at the swap that it started the login. RFC 9700 says public clients, such as mobile apps, must use it, and recommends it for server apps too.
- Can one app support both SAML and OIDC?
- Yes, and business software often does. Keep a config entry per customer that names its standard, then turn either result into the same internal session. Some teams put an identity broker in front, which speaks both and hands the app plain OIDC.
- Does single sign-on log users out of every app at once?
- Not by itself. Disabling a user at the identity provider blocks new logins, but sessions already open in each app may keep working until they end. Keep app sessions short, revoke refresh tokens, and consider SCIM, a standard for pushing account changes to apps.
Sources and further reading
- RFC 6749: The OAuth 2.0 Authorization Framework (IETF, October 2012)
- OpenID Connect Core 1.0 incorporating errata set 2 (OpenID Foundation)
- RFC 9700: Best Current Practice for OAuth 2.0 Security (IETF, January 2025)
- RFC 7636: Proof Key for Code Exchange by OAuth Public Clients (IETF, September 2015)
- SAML V2.0 Technical Overview (OASIS committee draft, 2008)
- Assertions and Protocols for SAML V2.0 (OASIS Standard, 15 March 2005)
- On Breaking SAML: Be Whoever You Want to Be (Somorovsky and others, USENIX Security 2012)
- RFC 7644: SCIM Protocol (IETF, September 2015)
- The OAuth 2.1 Authorization Framework, Internet-Draft status (IETF Datatracker)
- PyJWT release history (PyPI)