How OAuth 2.0 works
How OAuth 2.0 enables delegated authorization β Authorization Code flow, PKCE for mobile, token types, scope enforcement, and how OpenID Connect adds identity on top.
The Problem Statement
Interviewer: "You click 'Sign in with Google' on a third-party app. Walk me through exactly what happens from that click to the moment the app knows who you are and has permission to read your Google Drive files."
This question is a staple in security-focused interviews and senior backend rounds. It tests whether you understand the difference between authentication and authorization, why delegated access exists as a concept, and whether you can trace an OAuth 2.0 flow at the protocol level rather than just waving your hands at "it redirects to Google." Most candidates get the broad strokes right but miss the important security properties: the state parameter, PKCE, the authorization code exchange, and why the flow is designed the way it is.
The hidden rubric here is about trust boundaries. OAuth is a protocol for granting controlled access without sharing credentials. Every design decision in the protocol is motivated by a specific threat. Strong candidates name those threats.
Interviewers at senior levels are also listening for whether you understand the difference between the OAuth framework and specific flows. OAuth 2.0 defines a framework with four grant types. Knowing when to use each one, and why the Implicit grant was deprecated, signals that you understand how the protocol evolved in response to real-world security failures. The interviewer is not just testing knowledge of the happy path. They want to know whether you have read the security considerations in the spec, or just followed a tutorial.
A strong answer covers three tiers: the protocol mechanics (what happens), the security properties (why each step exists), and the operational decisions (where to store tokens, how to handle revocation, when to use which grant). Candidates who only cover mechanics rarely pass senior-level screens.
Clarifying the Scenario
Before diving in, I always scope the answer.
You: "Quick clarification: when you say 'sign in with Google,' are we talking about a web application with a server-side backend, a mobile app, or a single-page app?"
Interviewer: "Web app with a server-side backend first. Then I'll ask about mobile."
You: "And are we just doing authentication (verifying who the user is), or does the app also need to make API calls on the user's behalf, like reading Drive files?"
Interviewer: "Both. The app authenticates the user and needs Drive read access."
You: "Got it. I'll cover the Authorization Code flow for the web app case, then explain how PKCE changes things for mobile and SPAs. Then I'll explain how OpenID Connect layers on top of OAuth to handle the authentication part."
Separating OAuth (authorization) from OpenID Connect (authentication) in your clarification immediately signals that you understand the protocol distinction. Many candidates conflate the two.
My Approach
OAuth 2.0 is a framework for delegated authorization. The core idea: you let a user grant a third-party application limited access to their resources at another service, without sharing their password.
Before OAuth, the common pattern for third-party integration was credential sharing: you gave the third-party app your username and password and it logged in as you. This is a terrible design. The app gets full account access, you cannot revoke access without changing your password everywhere, and the third party can impersonate you for anything at any time. OAuth was designed to replace this anti-pattern with a system where the user grants scoped, revocable access without ever sharing credentials.
I break the answer into five parts:
- The delegation problem: Why OAuth exists and what it replaces.
- The Authorization Code flow: The main protocol for server-side apps, step by step, with every security decision named.
- PKCE: How the same flow adapts to mobile and browser-based apps that cannot keep secrets.
- Tokens: Access tokens, refresh tokens, JWT structure, and rotation policies.
- OpenID Connect: How identity is layered on top of OAuth authorization, and why they are two separate protocols.
Understanding each part in isolation, then seeing how they compose, is how I explain this without losing the thread. The key mental model: every step in the OAuth flow blocks a specific attack. When I describe a step, I name the attack it prevents. That is what separates a walk-through of the mechanics from a demonstration of deep protocol understanding.
The Four Grant Types
Before tracing the Authorization Code flow in detail, I want to map the four grant types quickly. Interviewers often ask which grant to use for a given scenario, and mixing them up is a common mistake.
| Grant Type | Use Case | Has User? | Client Secret? |
|---|---|---|---|
| Authorization Code (+ PKCE) | Server-side web apps, mobile, SPAs | Yes | Yes (web) / No (mobile/SPA) |
| Client Credentials | Service-to-service, background jobs | No | Yes |
| Device Code | Smart TVs, CLIs, headless devices | Yes | Optional |
| Implicit (Deprecated) | Old SPAs; do not use | Yes | No |
The Device Code grant is worth knowing for interviews because it comes up in smart TV and IoT scenarios. The device shows a short code and a URL. The user visits the URL on their phone, enters the code, and authenticates. The device polls the token endpoint every few seconds until the user completes the flow. No redirect URI, no browser on the device. Spotify and Netflix use this for TV apps.
The Implicit grant is the one that no longer belongs in your answer. If an interviewer mentions it, acknowledge it is deprecated and explain why: it puts the access token directly in the URL fragment, which is a log-injection and XSS risk, and it cannot issue refresh tokens safely.
The Device Code grant is worth knowing for interviews because it comes up in smart TV and IoT scenarios. The device shows a short code and a URL on screen. The user visits the URL on their phone or laptop, enters the code, and authenticates with the full interactive flow. The device polls the token endpoint every few seconds (the interval field tells it how often) until the user completes the flow. No redirect URI, no browser on the device. Spotify and Netflix use this for TV apps. CLIs like the GitHub CLI and the Heroku CLI use it for authenticating developers on the command line.
The ROPC (Resource Owner Password Credentials) grant deserves a special mention because you will encounter it in legacy systems. The user's username and password go directly to the app, which exchanges them for tokens at the token endpoint. This completely defeats the point of OAuth: the user is handing their credentials to a third party instead of only to the identity provider. It is removed in OAuth 2.1. When you encounter it in a system, flag it for migration to Authorization Code with PKCE.
The authorization server's role is often abstracted by identiy platforms (Auth0, Okta, Cognito, Keycloak), but understanding what it does is important. It: stores client registrations (client_id, client_secret, allowed redirect URIs, allowed scopes), authenticates users, manages consent records, issues tokens, maintains the JWKS endpoint, handles token revocation, and enforces token policies (expiry, rotation). For interviews, think of it as the trust anchor of the entire system. If it is compromised, all tokens from it are untrusted.
The Architecture
Here is the full OAuth 2.0 Authorization Code flow with OpenID Connect:
Every arrow in this flow has a specific security purpose. Walk through the critical ones.
The redirect from App to Google carries state: a random, unpredictable value generated by the app and stored in the user's session. When Google redirects back with the code, the app checks that the state parameter matches. This is CSRF protection: without state, an attacker could trick a user's browser into completing an OAuth flow with the attacker's authorization code, binding the victim's account to the attacker's session.
The authorization code is short-lived (typically 60 seconds), single-use, and useless on its own. It must be exchanged for tokens by the app's server, using the app's client secret. The code never becomes part of a URL that might be logged to access logs. The access token, which is the sensitive credential, travels directly from the authorization server to the app's backend, never through the user's browser.
The scope string in the initial redirect lists what the app wants permission to do. The authorization server presents a consent screen to the user showing exactly what the app is requesting. The user can approve or deny. If approved, the issued token contains the granted scopes as a claim. The app should not assume it received all the scopes it asked for: users can sometimes grant partial consent.
The redirect_uri in the request must match a pre-registered value exactly. If the authorization server allows wildcard matching or path prefix matching, an open redirect vulnerability exists: an attacker can craft a login URL pointing to a redirect_uri that sends the code to an attacker-controlled server. Strict exact matching is required.
Why two steps: code then token?
The two-step exchange exists to keep the access token off the browser. If Google returned the access token directly in the redirect URL, it would appear in browser history, server access logs, and the Referer header of the next request. The authorization code is worthless without the client secret, so it can safely transit the browser.
One more step worth naming: when the authorization server issues the access token, it also checks whether the client is allowed to request the scopes in the token. This is the client's pre-registered scope allowlist. Even if the user is willing to consent to every scope imaginable, the authorization server will only issue scopes that the client registration allows. This is a second enforcement layer: the scope claim in the token reflects the intersection of what the user consented to and what this client is permitted to receive.
Error handling in the redirect is also important. If the user denies consent, or if the client_id or redirect_uri is invalid, the authorization server's behavior differs. For a denied consent, it redirects back with error=access_denied and the state value. Your app must handle this: show the user a message, do not retry automatically. For invalid client_id or redirect_uri, the authorization server must NOT redirect at all (doing so would be an open redirect that an attacker could exploit). It should show an error page directly.
The token endpoint response is worth knowing in detail. A successful exchange returns access_token, token_type (always Bearer), expires_in (seconds until expiry), and optionally refresh_token, scope, and id_token (if openid was requested). Always check the scope in the response: some providers issue a reduced scope if the user granted only partial consent, or if the requested scope is not supported. If the response scope differs from what your app requires to function, handle it gracefully and prompt re-authorization rather than assuming you have all the permissions you asked for.
The Authorization Code Flow and PKCE
PKCE (Proof Key for Code Exchange, pronounced "pixie") solves a problem with the Authorization Code flow in environments where you cannot keep a client secret: mobile apps and single-page apps.
In a native mobile app, the "client secret" would be embedded in the binary. Anyone can decompile the app and extract it. So there is no meaningful secret. The same is true for a browser-based SPA: the source code is visible to any user. The client secret cannot actually be kept secret.
PKCE replaces the client secret with a per-request cryptographic challenge. The app generates a random code_verifier, computes code_challenge = BASE64URL(SHA256(code_verifier)), and sends the challenge to the authorization server with the initial request. It keeps the verifier secret locally. When exchanging the code for tokens, the app sends the original verifier. The server computes the hash and compares it to the stored challenge. If they match, the exchange is legitimate.
The threat PKCE defends against: an attacker on the same device intercepts the authorization code from the redirect (possible on mobile via malicious apps registered to the same custom URL scheme). Without PKCE, they could exchange the code for tokens. With PKCE, they cannot: they have the code but not the code_verifier. The verifier was kept in memory by the legitimate app and never leaves the device.
The code_verifier requirements are specific: it must be between 43 and 128 characters, using only URL-safe characters (A-Z, a-z, 0-9, hyphen, underscore, period, tilde). Shorter verifiers reduce the entropy and make brute-force attacks on the challenge more feasible. Most OAuth libraries generate a 64-character random verifier by default.
The S256 method (SHA-256 of the verifier) is mandatory for production. There is a plain method where the challenge equals the verifier, but it provides no security benefit because an attacker who intercepts the challenge can use it directly as the verifier. Specify code_challenge_method=S256 explicitly and reject any authorization server that does not support it.
For server-side web apps, PKCE provides defense in depth even when you also use a client secret. The client secret protects against unauthorized clients. PKCE protects against code interception even by legitimate-looking clients that somehow obtained the code. The OAuth 2.1 draft mandates PKCE for all confidential clients too, not just public ones.
PKCE is now recommended everywhere, not just mobile
The OAuth 2.1 draft mandates PKCE for all authorization code flows, including server-side web apps. Even though server-side apps can use a client secret, PKCE provides defense in depth against code interception. Use PKCE plus client secret for server-side apps, and PKCE without client secret for mobile and SPAs.
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with SDEpedia Premium.
Related Articles
What actually happens in the TLS 1.3 handshake: ClientHello, ServerHello, key exchange, certificate verification, and how both parties derive symmetric session keys without ever transmitting them.
Understand the WebSocket protocol: the upgrade handshake, bidirectional framing, connection lifecycle, and scaling challenges, plus when to pick WebSockets vs Server-Sent Events vs long polling for real-time features.