Part 9 · Security for System Designers

OAuth 2.0 & OpenID Connect Explained

Apps used to ask for your Google password to print your photos. How OAuth 2.0 hands out limited, expiring, revocable “valet keys” instead, how OpenID Connect adds login on top, and the flows and checks that keep it secure.

Lesson 5012 min readintermediate#security#oauth

On this page

  1. The Problem
  2. The Core Idea
  3. How It Works
  4. Trade-offs
  5. In the Real World
  6. Interview Questions
  7. Key Takeaways
  8. Further Reading

The Problem

A photo-printing website wants to print photos from your Google Photos. In the old days, it would ask for your Google password. That meant:

  • the printing site could read your email, delete your files, and change your password,
  • if the printing site got hacked, your whole Google account was exposed,
  • the only way to stop it was to change your password, which also broke every other app.

Today you click "Sign in with Google" or "Allow access to your photos", approve a screen listing exactly what's being shared, and never type your Google password anywhere else. This is done with two standards. OAuth 2.0 is a standard way to let one app access your data in another service without your password. OpenID Connect is a standard way to log in with an account you already have.

They are behind almost every "Continue with Google/Apple/Microsoft/GitHub" button, and behind most modern API security. Many people misunderstand and misuse them.

The Core Idea

Think of a valet key for a car.

  • You give the valet a special key that can start the car and drive it a short distance, but can't open the boot or the glovebox.
  • The valet never gets your main key.
  • You can take the valet key back at any time.

OAuth 2.0 is a system for handing out valet keys to apps. The official name of a valet key is an access token: a small piece of text that an app sends to an API to prove it has permission. These keys are:

  • limited in what they can do (scopes),
  • limited in time (expiry),
  • revocable,
  • and without sharing your password.

OpenID Connect (OIDC) is a thin layer on top of OAuth. It adds an ID card: a standard way for the app to learn who you are, so it can log you in.

OAuth 2.0 = authorization ("this app may access these things"). OpenID Connect = authentication ("this person is Asha, verified by Google").

How It Works

The four roles

RoleWho it isExample
Resource ownerThe user who owns the dataYou
ClientThe app that wants accessThe photo-printing website
Authorization serverThe server that gives out tokens after the user approvesGoogle's login and consent service
Resource serverThe API holding the dataThe Google Photos API

Key terms

  • Access token: the "valet key" the client sends to the API in an HTTP header: Authorization: Bearer <token>. "Bearer" means whoever holds the token can use it. It is short-lived (minutes to an hour).
  • Refresh token: a token used to get new access tokens without asking the user again. It lives for a long time, must be stored safely, and can be revoked (cancelled) (post 49).
  • Scopes: a scope is a name for what the token allows, such as photos.read, calendar.readonly or repo:status. Ask for the fewest scopes you need.
  • Consent: the screen where the user approves the requested scopes.
  • Redirect URI: the address where the authorization server sends the user back after approval. You must register it in advance, and the server checks that it matches exactly.
  • Client ID / client secret: these identify the app. The client ID is public. The client secret is like the app's password. Confidential clients (server-side apps) can keep a secret. Public clients (mobile apps, single-page apps) cannot, because anyone can extract anything that is in their code.

The main flow: Authorization Code + PKCE

A "flow" is the set of steps an app follows to get a token. This is the recommended flow for almost every app: server-side web apps, single-page apps and mobile apps.

step 1 / 11
User
Client app
Auth server
API
  1. Connect Google Photos
  2. ↻ code_verifier (random), code_challenge = SHA256(verifier)
  3. redirect to /authorize · scope, state, code_challenge
  4. log in + approve “photos.read”
  5. redirect back · code=ABC123&state=xyz
  6. ?code=ABC123&state=xyz
  7. ↻ check state == xyz
  8. POST /token · code + code_verifier
  9. access token (+ refresh, id_token)
    SHA256(verifier) matched
  10. GET /photos · Bearer access token
  11. photos ✅
Authorization Code + PKCE — the recommended flow for almost every app. The code travels through the browser; tokens never do. · sequence · step through

Why two steps ("code first, then token")? The authorization code travels through the browser, in the redirect URL. This is a risky place. The code is short-lived and can be used only once, so it is useless on its own. The tokens are fetched in a separate, direct request from the client to the authorization server.

What PKCE adds. PKCE (Proof Key for Code Exchange, said "pixy") is an extra step that protects against an attacker who steals the authorization code. For example, a malicious app on a phone may register the same redirect address as your app.

  • The client creates a random code_verifier and sends only its hash (code_challenge) at the start. A hash is a one-way fingerprint: you cannot get the verifier back from it.
  • To swap the code for tokens, the client must present the original verifier.
  • An attacker who has only the stolen code cannot complete the exchange.

PKCE was first made for mobile apps. Now it is recommended for all clients, including server-side ones.

What state does. The state parameter is a random value. The client creates it and checks it when the user comes back. It stops CSRF-style attacks (forged requests), where an attacker tricks your app into accepting their authorization code and linking their account.

Client Credentials: machine-to-machine

Use this flow when no user is involved. For example, a backend service calls another company's API, or one internal service calls another:

step 1 / 4
Service A
Auth server
API
  1. POST /token · grant_type=client_credentials, scope=reports.read
  2. access token
    authenticated by secret, private-key JWT or mTLS
  3. GET /reports · Bearer …
  4. 200 OK
Client Credentials — machine to machine, no user involved. · sequence · step through

The service logs in as itself. Keep its secret safe (post 51). Even better, use private-key JWT client authentication (the service signs a token with its private key) or mTLS (both sides show certificates) instead of a shared secret.

Device Authorization flow

Use this flow for devices that have no browser or easy keyboard, like smart TVs, game consoles and command-line tools:

step 1 / 6
TV
Auth server
Your phone
  1. request device code
  2. user code WDJB-MJHT + verification URL
  3. ↻ show “go to example.com/activate, enter WDJB-MJHT”
  4. enter code, log in, approve
  5. poll: approved yet?
  6. access + refresh tokens
The Device Authorization flow — how you log in on a TV. · sequence · step through

That's why you log into streaming apps on a TV by typing a short code on your phone.

Deprecated flows: don't use these

  • Implicit flow: tokens were returned directly in the browser URL. They leaked through browser history, logs and referrer headers (a header that tells a site which page the user came from). It is replaced by Authorization Code + PKCE.
  • Resource Owner Password Credentials: the app collects the user's password and sends it to the auth server. This defeats the whole point of OAuth. Do not use it.

The OAuth 2.0 Security Best Current Practice (RFC 9700) and the OAuth 2.1 draft put these rules together: PKCE for everyone, no implicit or password grants, exact redirect URI matching, and tokens that are short-lived, tied to the sender, or rotated. (An RFC is an official internet standards document. A "grant" is another word for a flow.)

Access token formats

  • Opaque tokens: random strings with no meaning that the API can read. The API must ask the authorization server "is this token valid, and what are its scopes?" This is called token introspection. Opaque tokens are easy to revoke, but they add one call per request (you can cache the answer).
  • JWT access tokens: these carry their own data and are signed (post 49). The API verifies them locally using the authorization server's public keys, which it gets from the server's JWKS endpoint (a URL that lists the keys). This is fast, but revoking a token relies on short expiry.

The API must validate every token:

  • the signature (for JWTs),
  • expiry (exp),
  • issuer (iss): is it from our authorization server?
  • audience (aud): was it issued for this API? A token meant for another API must be rejected.
  • scopes: does this token allow this action?

OpenID Connect: adding login

OAuth alone does not tell the client who the user is. An access token is meant for the API, not for the client to read. Early "Login with X" features misused access tokens to find out who the user was, and this caused security bugs. OpenID Connect (OIDC) fixes this.

What OIDC adds:

  • The openid scope: if you ask for it, you are saying "I want to log the user in".
  • The ID token: a JWT for the client that describes the user who logged in. Here is an example. The arrow note is only for explanation:
JavaScript
{  "iss": "https://accounts.google.com",  "sub": "110169484474386276334",      ← stable, unique user ID at this provider  "aud": "your-client-id",  "exp": 1803000000,  "iat": 1802996400,  "nonce": "n-0S6_WzA2Mj",  "email": "asha@example.com",  "email_verified": true,  "name": "Asha Kulkarni"}
  • The userinfo endpoint: a URL where you can get more profile details by using the access token.
  • Discovery: a standard URL (/.well-known/openid-configuration) lists the provider's endpoints, supported features and JWKS keys, so libraries can configure themselves automatically.
  • The nonce: a random value that the client sends and then looks for in the ID token. It prevents replay attacks, where an attacker re-uses an old token.

ID token vs access token:

ID tokenAccess token
PurposeTells the client who logged inLets the client call an API
Audience (aud)The client appThe API (resource server)
Who reads itThe clientThe API
Send it to APIs?❌ No✅ Yes

Identify users by iss + sub, not by email. Emails can change, and the same email can exist at different providers. The sub value stays the same for one user at one provider.

Security checklist

  • ✅ Use Authorization Code + PKCE (and state, and nonce for OIDC).
  • ✅ Register exact redirect URIs, with no wildcards. An open redirect (a page that sends users to any address it is given) is a classic way to steal tokens.
  • ✅ Request minimal scopes. Ask for more only when the user really needs the feature. This is called "incremental consent".
  • ✅ Keep access tokens short-lived. Rotate refresh tokens and store them securely.
  • ✅ Validate every token: signature, exp, iss, aud and scopes.
  • ✅ For single-page apps, prefer a BFF (post 13) that keeps tokens on the server, giving the browser only an HttpOnly session cookie.
  • ✅ Protect client secrets, or avoid them with private-key JWT or mTLS client authentication.
  • ✅ Let users see and revoke connected apps.
  • ✅ Use a well-tested library or identity provider. Do not write OAuth yourself from scratch.

Build or buy?

Running your own authorization server means you must handle login, MFA, passkeys, account recovery, token signing, key rotation (changing keys regularly), consent, security patches and legal rules (compliance). So most teams use one of these instead:

  • managed identity providers (another company runs them for you): Auth0/Okta, Microsoft Entra ID, AWS Cognito, Google Identity Platform, Firebase Auth, Clerk,
  • open-source servers that you run yourself: Keycloak, Ory, Authentik, Zitadel,
  • social login: Google, Apple, Microsoft, GitHub.

Trade-offs

  • OAuth/OIDC: no password sharing, fine-grained scopes, revocable access and SSO. But the flows are complex, redirects and tokens are easy to misconfigure, and you depend on the identity provider.
  • Opaque tokens: easy revocation and hidden contents, but an introspection call per request. JWT access tokens: fast local checks, but revocation relies on short expiry.
  • Social login: fewer passwords and less friction, but you depend on external providers, and some users don't want to link accounts.
  • Managed IdP: fast, secure and full-featured, but costs money and adds vendor lock-in. Self-hosted (Keycloak): full control, but you own the operations and security patching.

In the Real World

"Sign in with Google / Apple / Microsoft." These buttons use OpenID Connect. The app receives an ID token proving who you are, and never sees your password. Apple's version also lets users hide their real email, giving each app a unique relay address.

GitHub OAuth apps and fine-grained tokens. A CI service (a service that builds and tests your code automatically) or a code-review tool may ask to access your GitHub repositories. When it does, it uses OAuth with scopes. GitHub also offers fine-grained tokens that limit access to specific repositories and permissions, which is least privilege in practice.

Smart TV logins. Logging into YouTube, Netflix or other apps on a TV by visiting a URL on your phone and entering a short code is the Device Authorization flow in action.

Open banking. In many countries, open-banking laws let third-party apps (budgeting tools, payment apps) read bank data or start payments, but only with the customer's clear consent. They use security profiles based on OAuth 2.0 with strict extra rules.

Enterprise SSO. Companies use identity providers like Okta or Microsoft Entra ID so employees log in once and access Slack, email, HR tools and internal apps through OIDC or SAML (SAML is an older XML-based login standard, common in companies). When someone leaves, disabling one account removes access everywhere.

Interview Questions

Q1What's the difference between OAuth 2.0 and OpenID Connect?

OAuth 2.0 is delegated authorization: a client gets a scoped, expiring access token to call an API on the user's behalf. OpenID Connect adds authentication on top: the openid scope, an ID token (a JWT for the client describing who logged in), a userinfo endpoint and discovery.

Q2Why use the authorization code flow instead of returning tokens directly?

The redirect passes through the browser, where URLs leak into history, logs and referrers. A short-lived, single-use code is useless alone; the client swaps it for tokens in a direct back-channel request — and PKCE ensures only the client that started the flow can do so.

Q3What does PKCE protect against?

Stolen authorization codes. The client sends a hash of a random verifier at the start and must present the original verifier to redeem the code, so an attacker who intercepts the code can't exchange it. It's now recommended for all clients.

Q4What must an API check on every access token?

Signature (or introspection for opaque tokens), expiry, issuer (our authorization server), audience (issued for this API) and scopes (allows this action).

Q5ID token vs access token?

The ID token is for the client — it proves who authenticated and is never sent to APIs. The access token is for the resource server. Identify users by iss + sub, not by email.

Q6Which OAuth flows should you avoid?

The implicit flow (tokens in the browser URL) and the resource owner password grant (the app collects the user's password). Both are deprecated in OAuth 2.1 in favour of Authorization Code + PKCE.

Key Takeaways

  • OAuth 2.0 is delegated authorization. Apps get limited, expiring, revocable access tokens (with scopes) without the user's password.
  • The four roles are resource owner, client, authorization server and resource server.
  • Use Authorization Code + PKCE (with state) for user-facing apps, Client Credentials for machine-to-machine, and Device flow for TVs and CLIs. Avoid the implicit and password grants.
  • OpenID Connect adds login: the ID token is for the client, the access token is for the API. Identify users by iss + sub.
  • Validate every token (signature, exp, iss, aud, scopes), register exact redirect URIs, keep tokens short-lived, and use proven libraries or identity providers.

Further Reading

  • RFC 6749 (The OAuth 2.0 Authorization Framework) and RFC 7636 (PKCE)
  • RFC 9700 (OAuth 2.0 Security Best Current Practice) and the OAuth 2.1 draft
  • The OpenID Connect Core 1.0 specification
  • The oauth.net website (a community guide to OAuth 2.0 and 2.1)
  • OAuth 2.0 Simplified by Aaron Parecki (also available as a free online guide)
  • The Keycloak documentation (to run your own identity provider locally and experiment)

Published by Jainik · Last updated · How these lessons are made

Keep learning

Related series