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.)
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.
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.”
example.com's identity hub. It issues the tokens AcmeNetworks consumes. In this story it holds no passwords itself — it federates “upstream” to Microsoft.
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:
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.
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:
- Authentication (authn) — who are you? Proving identity. Logging in.
- Authorization (authz) — what may you do? Granting permissions.
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.
openid scope, UserInfo endpoint, discovery documentExtending 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 term | Plain English | In this example |
|---|---|---|
| Resource Owner | The user who owns the identity/data | Alice |
| Client / Relying Party | The app that wants tokens | AcmeNetworks.com |
| Authorization Server / OpenID Provider (OP) / IdP | The service that authenticates users and mints tokens | Okta (example.okta.com) |
| Resource Server | The API that accepts access tokens | Acme's own API (and any API Okta protects) |
| Scope | A named permission the client asks for | openid profile email |
| Grant | A recipe for obtaining tokens | Authorization 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
- Front channel — messages relayed via browser redirects (HTTP 302s). Visible to the user agent, so only non-secret, short-lived things travel here: the authorization request and the one-time code.
- Back channel — a direct HTTPS call between Acme's server and Okta's token endpoint. The browser never sees it. Secrets and tokens live here.
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:
// 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:
- Microsoft session at
login.microsoftonline.com— “Alice proved her password + MFA here.” - Okta session at
example.okta.com— “Alice has an active SSO session with her company hub.” - Acme session at
app.acmenetworks.com— an ordinary app session, like any web login.
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
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.email, sub, groups. Scopes are what you ask for; claims are what you get back.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, but baked into the ID token so a token minted for one login can't be replayed into another.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./.well-known/openid-configuration — a JSON map of all its endpoints and keys. Clients configure themselves from one URL.@example.com → example.com's Okta). Also called IdP discovery; Okta implements it with routing rules.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:
- An Illustrated Guide to OAuth and OpenID Connect — David Neal, Okta. The classic cartoon-style explainer; source of the terminology framing used here.
- Diagrams and Movies of All the OAuth 2.0 Flows — Takahiko Kawasaki. Every grant type, animated. The best single reference once you know the basics.
- An Introduction to OAuth 2 — DigitalOcean. Concise text walkthrough of the four classic grant types.
- Okta: External Identity Providers and Okta ↔ Microsoft Entra ID setup guide. The exact inbound-federation configuration from Part 6, plus IdP routing rules.
- Microsoft identity platform: OAuth 2.0 authorization code flow. The Entra-side view of the same flow, with real request/response examples.
- oauth.net/2 & openid.net: How Connect Works. The standards' own front doors — RFC 6749, RFC 7636 (PKCE), OIDC Core.
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.