sauble.ai

Primer on OAuth 2.0 & OpenID Connect

One login, four parties, zero passwords shared. We follow a single sign-in — an example.com employee logging into AcmeNetworks.com through Okta and Microsoft — and watch every redirect, code, and token move between the players, step by step.

Varma Chanderraju · Sauble Engineering · July 2026

Series: 1 · OAuth 2.0 & OIDC  —  2 · Headless OAuth 2.0  —  3 · MCP Authorization

Part 1 · The scenario

Meet the cast

Alice works at example.com. Her company is a Microsoft shop — her real corporate identity (username, password, MFA) lives in Microsoft Entra ID (formerly Azure AD, the thing behind Outlook/Office 365 logins). example.com uses Okta as its identity hub. Today Alice wants to use AcmeNetworks.com, a network-monitoring SaaS her company just bought, using her enterprise identity — no new account, no new password.

(Okta and Microsoft are just concrete stand-ins: Okta plays the identity provider role — in OAuth terms, the authorization server — and Entra ID is the upstream identity source where credentials actually live. Swap in Auth0, Ping, Keycloak, or Google Workspace and every flow in this primer runs identically; the protocol doesn't care about vendors.)

Alice + her browser
Resource Owner / End User

The human who owns the identity. Her browser is the courier that gets bounced between all the other parties — it carries messages but is never trusted with secrets.

AcmeNetworks.com
Client / Relying Party (RP)

The app Alice wants to use. It outsources login entirely: it never sees her password and couldn't check it if it wanted to. It relies on someone else's word — hence “relying party.”

Okta (example.okta.com)
Authorization Server / IdP / OP

example.com's identity hub. It issues the tokens AcmeNetworks consumes. In this story it holds no passwords itself — it federates “upstream” to Microsoft.

Microsoft Entra ID
Upstream IdP (source of truth)

Where Alice's credentials actually live. The only party that ever sees her password, and the one that runs MFA.

The punchline, up front: when this flow finishes, AcmeNetworks knows exactly who Alice is — and holds delegated tokens scoped to exactly what she consented to — yet her password traveled to exactly one place: Microsoft. Not to Acme. Not even to Okta. Everything else moves as short-lived, signed, revocable tokens. That trick is the entire point of OAuth 2.0 and OpenID Connect.

Part 2 · The problem OAuth solves

Why not just… send the password?

Before OAuth (~2007), if an app needed access to your data in another system, you typed your password into the app itself, and it logged in pretending to be you. Everyone did this. It was terrible:

The old anti-pattern

Password sharing

  • Acme stores Alice's corporate password — every such app is now a breach target for example.com.
  • Acme gets all her powers, forever — no way to grant “read-only, for 1 hour.”
  • Revoking access means changing the password, which breaks every other app.
  • No MFA, no device checks — the app replays a raw password.
The OAuth pattern

Delegated tokens

  • Alice authenticates only at her identity provider, which she already trusts.
  • Acme receives a scoped, expiring access token — a key card, not the master key.
  • example.com's admin can revoke Acme's access centrally without touching Alice's password.
  • MFA, device posture, and geo policies run at the IdP, uniformly for every app.

A useful mental model throughout: the access token is a hotel key card. The front desk (IdP) checks your ID once, then hands you a card that opens your room and the gym — but not the manager's office — and stops working on checkout day. The hotel doesn't give you a copy of the building's master key, and shops in the lobby never see your ID.

Part 3 · Two standards, one flow

OAuth 2.0 is authorization. OIDC adds authentication.

These two words look similar and are constantly confused, so let's pin them down:

OAuth 2.0 (RFC 6749, 2012) was designed purely for authorization: “let this app read my calendar.” It deliberately says nothing about who the user is. But the industry needed federated login too, and people started abusing OAuth for it — badly. So in 2014 the identity community standardized OpenID Connect (OIDC): a thin layer on top of OAuth 2.0 that adds identity. Same flow, same redirects, plus one new artifact — the ID token — and standard endpoints for discovering and verifying it.

Extending the hotel analogy: OAuth hands you the key card (access token — opens doors, says nothing about you). OIDC also hands you a passport (ID token — signed proof of who you are, issued by an authority both sides trust). Alice's login to AcmeNetworks needs the passport; Acme's API calls on her behalf use the key card.

Spec termPlain EnglishIn this example
Resource OwnerThe user who owns the identity/dataAlice
Client / Relying PartyThe app that wants tokensAcmeNetworks.com
Authorization Server / OpenID Provider (OP) / IdPThe service that authenticates users and mints tokensOkta (example.okta.com)
Resource ServerThe API that accepts access tokensAcme's own API (and any API Okta protects)
ScopeA named permission the client asks foropenid profile email
GrantA recipe for obtaining tokensAuthorization Code grant (the one below)

Part 4 · The core flow, animated

The Authorization Code flow

This is the flow behind virtually every “Sign in with …” button, and the one OIDC recommends for web apps. Let's first run it in the simple case — pretend for a moment that Okta itself holds Alice's password (we'll add Microsoft in Part 6). Watch two things: which channel each message travels on, and where the password does and doesn't go.

Step through with the buttons (or ←/→ arrow keys after clicking the diagram). Dashed arrows pass through the browser; solid arrows are direct server-to-server calls.

Why the code-for-token dance? The authorization code travels through the browser — the front channel — where URLs leak into history, logs, and referrers. So the code alone is useless: it's one-time, expires in seconds, and can only be redeemed on the back channel by the real client proving itself with its client_secret (plus the PKCE verifier). Tokens themselves never touch a URL. An attacker who steals the code from the browser has a key to a lock they can't reach.

Front channel vs back channel

Part 5 · What Acme received

Anatomy of the tokens

The token response in step 9 contained three different objects with three different jobs:

ID token

The passport — OIDC

A signed JWT that answers “who logged in, when, and how.” Consumed by Acme itself to establish the login session, then set aside. Never sent to APIs.

Access token

The hotel key card — OAuth

Short-lived (minutes–an hour). Sent as Authorization: Bearer … on API calls. Scoped: it grants exactly what was consented, nothing more.

Refresh token

The re-check-in voucher

Long-lived, held server-side by Acme only. Exchanged quietly for fresh access tokens so Alice isn't re-prompted every hour. Revoking it kills the whole grant.

One nuance worth keeping straight: logging in and calling APIs on Alice's behalf are separate grants that happen to ride the same flow. This story requested identity-centric scopes (openid profile email), so the access token's reach is the IdP's own endpoints (like UserInfo) — Acme's own API trusts the app session Acme just established. The moment an app needs a third-party API on Alice's behalf, it requests API-specific scopes (telemetry:read, calendar.write, …) in the same authorization request, and the access token becomes the key to those. The companion primers build on that shape.

Reading a JWT

ID tokens are JWTs — JSON Web Tokens (and at Okta, access tokens happen to be JWTs too — though formally an access token is opaque to the client: its format is a private contract between the authorization server and the resource server, so clients validate ID tokens but should never parse access tokens). A JWT is three base64url chunks joined by dots. Not encrypted — anyone can decode it — but tamper-proof, because the third chunk is a digital signature over the first two:

eyJhbGciOiJSUzI1NiIsImtpZCI6Ik9rdGFLZXkxIn0.eyJpc3MiOiJodHRwczovL2FiYy5va3RhLmNvbSIsInN1YiI6IjAwdTFwcml5YSIsImF1ZCI6ImFjbWUtY2xpZW50LWlkIn0.QW4gUlNBIHNpZ25hdHVyZSBvdmVyIGhlYWRlci5wYXlsb2Fk…
header payload (claims) signature
// header — decoded
{
  "alg": "RS256",   // signature algorithm
  "kid": "OktaKey1" // which Okta key signed it
}
// payload — the "claims"
{
  "iss": "https://example.okta.com",  // issuer
  "sub": "00u1alice",             // stable user id
  "aud": "acme-client-id",        // minted FOR Acme
  "exp": 1751790000,              // expires
  "iat": 1751786400,              // issued at
  "nonce": "n-9dk2…",             // echoes step 2
  "email": "alice@example.com",
  "name": "Alice N.",
  "amr": ["pwd","mfa"]            // how she authenticated
}

Acme verifies the signature against Okta's published public keys (the JWKS endpoint), then checks iss (right issuer?), aud (minted for me?), exp (still valid?), and nonce (matches the value I sent — not a replay?). Five checks, no phone call to Okta needed. That's the elegance of signed tokens: trust is verified offline, cryptographically.

Part 6 · The full enterprise picture

Adding Microsoft: federation, or IdPs all the way up

Now the real scenario. example.com's passwords live in Microsoft Entra ID, not Okta. Okta is configured with Entra as an external identity provider — Okta calls this inbound federation. An IdP routing rule in Okta says: “users of example.com authenticate at Microsoft.”

The beautiful part: it's the same flow, nested. Okta simply plays both roles — it is the IdP from AcmeNetworks' point of view, and simultaneously a client/relying party from Microsoft's point of view. Each hop is an ordinary authorization-code exchange. AcmeNetworks has no idea Microsoft is involved, and Microsoft has no idea AcmeNetworks exists. Each party only trusts its immediate neighbor.

Chain of trust, not chain of secrets. Alice's password went to exactly one place: Microsoft (step 7). Microsoft vouched to Okta with a signed assertion. Okta vouched to Acme with a signed ID token. Nobody forwarded a credential — each link converted “I verified this person” into its own signed statement. This is what federation means: identity claims hop across trust boundaries; secrets never do.

The three sessions that now exist

After this one login, three independent cookies were set, and that's what makes SSO feel magical later:

Tomorrow, when Alice opens a second app — say her company's ticketing tool — steps 1–3 repeat, but at step 4 Okta sees its own valid session cookie and skips the entire Microsoft leg: it immediately mints a fresh code for the new app. One password prompt per day, dozens of apps. That's single sign-on: not one session shared everywhere, but one authentication event vouched for everywhere.

Where does SAML fit? You'll hear SAML constantly in enterprise SSO. It's the 2005-era predecessor to OIDC: same federation idea, but with signed XML documents posted through the browser instead of JSON tokens and REST calls. The Okta↔Microsoft leg in the diagram above is often configured as SAML instead of OIDC — the picture is identical, only the envelope format changes. New builds choose OIDC; SAML persists because enterprises already have it wired up.

Part 7 · Reference

Glossary — every term in one place

Scope
A named permission the client requests, space-separated: openid profile email offline_access. openid is the magic word that turns an OAuth request into an OIDC request; offline_access asks for a refresh token.
Claim
One fact inside a token: email, sub, groups. Scopes are what you ask for; claims are what you get back.
Redirect URI
The pre-registered callback URL (https://app.acmenetworks.com/callback) that codes may be delivered to. The IdP matches it exactly — this is the lock that stops attackers from siphoning codes to their own server.
state
A random value the client sends and expects echoed back, bound to the browser session. Defeats CSRF — a forged callback won't carry the right value.
nonce
Like state, but baked into the ID token so a token minted for one login can't be replayed into another.
PKCE (“pixy”)
Proof Key for Code Exchange. Client invents a random secret, sends its hash (code_challenge) in step 2, reveals the original (code_verifier) in step 8. Proves the redeemer of the code is whoever started the flow. Mandatory for mobile/SPA clients (which can't hold a client_secret), recommended for everyone.
Bearer token
“Whoever bears it may use it” — like cash. Which is why access tokens are short-lived and travel only over TLS on the back channel.
Discovery document
Every OIDC provider publishes /.well-known/openid-configuration — a JSON map of all its endpoints and keys. Clients configure themselves from one URL.
JWKS
JSON Web Key Set — the IdP's public signing keys, fetched from a URL in the discovery doc. What Acme uses to verify token signatures offline.
Consent
The “Acme wants to: view your profile ✓ Allow?” screen. In enterprises, admins usually pre-consent org-wide, so employees never see it.
Home realm discovery
How an app figures out which IdP a user belongs to — usually from the email domain (@example.com → example.com's Okta). Also called IdP discovery; Okta implements it with routing rules.
JIT provisioning
Just-In-Time: creating or linking the user's account automatically at first federated login (step 10 in the federation diagram), instead of pre-creating accounts by hand.
Grant types
Other recipes besides authorization-code: client credentials (no user — service-to-service, very relevant to MCP), device code (TVs/CLIs), and the deprecated implicit and password grants you should never use in new systems.

Part 8 · Go deeper

Sources & further reading

This tutorial synthesizes (and re-narrates through the example.com scenario used here) the best existing material on the open web:

Where the story continues: two companion primers build on this foundation. Primer on Headless OAuth 2.0 covers what happens when there is no browser — or no human at all: client credentials, the device grant, refresh tokens, and token exchange. Primer on MCP Authorization then shows how the Model Context Protocol applies OAuth 2.1 so MCP clients can securely connect to MCP servers they have never seen before — protected-resource metadata for discovery, client ID metadata documents for introductions, and resource indicators (RFC 8707) for binding each token to one specific server. Same cast, bigger stage.