Skip to content
Backend & systems

OAuth 2.0 Flow Diagram and Types: Authorization Code with PKCE First

Most OAuth confusion comes from learning the grants as a list of names instead of asking who the client is and whether a user is present. This guide starts with the flow you will use most often, authorization code with PKCE, as a sequence diagram you can redraw on a whiteboard. It then covers client credentials, the device grant, why the implicit and password grants are deprecated, and where OpenID Connect fits. A Python snippet checks an S256 code challenge against the RFC 7636 test vector.

By Published

8 min readEditorial analysisUpdated
  • OAuth 2.0
  • PKCE
  • OpenID Connect
  • authentication
  • API security
What to remember

Pick the grant with two questions: is a user present, and can the client keep a secret? Users in a browser or app get authorization code with PKCE, machines get client credentials, and input-limited devices get the device grant. The implicit and password grants are not options for new systems.

Roles, client types, and the flow map

RFC 6749 defines four roles. The resource owner is usually the user. The client is the application that wants access. The authorization server authenticates the user and issues tokens, and the resource server is the API that accepts them. Clients are confidential when they can protect a credential, such as a backend web app, and public when they cannot, such as a single-page app, a mobile app, or a CLI. Anything shipped to a device can be extracted, so a public client has no real secret.

Two channels matter. The front channel is the browser redirect, which travels through URLs, history, and possibly logs. The back channel is a direct HTTPS call from the client to the token endpoint. Good flows put only short-lived, single-use values in the front channel and exchange them for tokens over the back channel. The table maps each grant to its use and its status under the OAuth 2.0 Security Best Current Practice, RFC 9700.

OAuth grant types and their status
GrantUse whenStatus
Authorization code + PKCEA user signs in through a browserRecommended (RFC 9700)
Client credentialsService to service, no userStandard (RFC 6749, 4.4)
Device authorizationTV or CLI without a usable browserStandard (RFC 8628)
Refresh tokenRenew access without the userRotate or bind for public clients
ImplicitLegacy browser appsSHOULD NOT be used
Resource owner passwordLegacy first-party appsMUST NOT be used

Authorization code with PKCE, step by step

The client generates a high-entropy code_verifier and derives code_challenge = BASE64URL(SHA-256(verifier)). It redirects the browser to the authorization endpoint with response_type=code, client_id, redirect_uri, scope, state, code_challenge, and code_challenge_method=S256. After the user signs in and consents, the authorization server redirects back to the registered redirect URI with a one-time code and the same state. The client checks state, then posts grant_type=authorization_code, the code, the redirect_uri, and the original code_verifier to the token endpoint.

The server hashes the verifier and compares the result with the stored challenge before issuing tokens. That is the whole trick: an attacker who intercepts the code from a redirect, a log line, or a malicious app registered for the same custom URL scheme cannot redeem it without the verifier, which never traveled through the front channel. RFC 9700 says public clients MUST use PKCE, recommends it for confidential clients, names S256 as the method that does not expose the verifier, and requires exact redirect URI matching.

Sequence diagram of the authorization code flow with PKCE between the client app, the authorization server and the API, in nine numbered steps
Steps 2 and 4 travel through the browser. The client redeems the code from step 4 in step 5, and tokens come back in step 7 only if the verifier hashes to the challenge sent in step 2. RecallDeck diagram

Compute an S256 challenge and check the RFC test vector

RFC 7636 requires a verifier of 43 to 128 characters drawn from letters, digits, and the four characters - . _ ~. The snippet uses secrets.token_urlsafe(32), which turns 32 random bytes into 43 URL-safe characters, and checks the format with a regular expression. The challenge is the raw SHA-256 digest encoded as base64url with the trailing = padding removed. Appendix B of RFC 7636 publishes a verifier and its expected challenge, which makes a deterministic test.

Running python3 pkce.py on Python 3.12 prints E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM, then True, then 43 43: the computed challenge matches the RFC value, and a fresh verifier and its challenge are both 43 characters long. A mismatch surfaces as an invalid_grant error at the token endpoint. The usual causes are standard base64 with + and /, leftover padding, hashing the hex digest instead of the raw bytes, or generating a new verifier between steps 2 and 5.

import base64, hashlib, re, secrets

def s256(verifier: str) -> str:
    digest = hashlib.sha256(verifier.encode("ascii")).digest()
    return base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")

def new_verifier() -> str:
    v = secrets.token_urlsafe(32)  # 32 random bytes -> 43 URL-safe characters
    assert re.fullmatch(r"[A-Za-z0-9\-._~]{43,128}", v)
    return v

# RFC 7636 Appendix B test vector
rfc_verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
expected = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"

print(s256(rfc_verifier))
print(s256(rfc_verifier) == expected)
v = new_verifier()
print(len(v), len(s256(v)))

Client credentials and the device authorization grant

When no user is involved, such as a billing service calling a reporting API, the client authenticates to the token endpoint as itself with grant_type=client_credentials and receives a token for its own permissions. RFC 6749 limits this grant to confidential clients, which authenticate with a client secret, a signed JWT assertion, or mutual TLS, and says a refresh token should not be included; the client simply asks again. Scope the client narrowly, because whoever holds its credential is that service.

The device grant in RFC 8628 serves TVs, consoles, and CLIs. The device requests a device_code, a short user_code, and a verification_uri, shows the code, and polls the token endpoint with grant_type urn:ietf:params:oauth:grant-type:device_code. Until the user approves on a phone or laptop, the server answers authorization_pending; slow_down means add five seconds to the polling interval. RFC 8628 warns about remote phishing: an attacker can start the flow and persuade a victim to enter the code, so RFC 8628 recommends telling the user that they are authorizing a device and asking them to confirm it is in their possession.

Why implicit and password grants are deprecated

The implicit grant returned the access token directly in the redirect URL fragment, so tokens could leak through manipulated redirect URIs, referrer headers, and browser history, and no standard mechanism bound them to the client that requested them. RFC 9700 says clients SHOULD NOT use it unless those leaks and token injection are otherwise prevented. The resource owner password credentials grant asks the client to collect the user's password itself, which exposes the credential to the client, trains users to type passwords outside the authorization server, and is not designed for two-factor or multi-step logins. RFC 9700 says it MUST NOT be used.

OAuth 2.1 consolidates these lessons, but cite it honestly: as of November 2026 it is still an Internet-Draft, draft-ietf-oauth-v2-1-17 from October 2026, not an RFC. The draft leaves out the implicit and password grants, requires exact redirect URI matching, and requires PKCE with the authorization code flow, except for confidential clients that correctly use the OpenID Connect nonce. In an interview, say that today's best practice is RFC 9700 and that OAuth 2.1 is the draft folding it into the core specification.

OAuth vs OpenID Connect

OAuth answers a delegation question: may this client call this API on the user's behalf, with these scopes? It does not tell the client who the user is, and an access token is meant for the resource server, not as proof of login for the client. OpenID Connect adds an identity layer on top of OAuth 2.0, most often with the authorization code flow. The client requests the openid scope and receives an ID token, a JWT addressed to the client with claims such as iss, sub, aud, exp, and nonce, plus access to a UserInfo endpoint.

The working rule: the ID token is for the client, and the access token is for the API. Validate the ID token's signature, issuer, audience, and nonce before trusting sub, and never forward it to APIs as a bearer credential. A common interview slip is to call OAuth an authentication protocol; a precise answer says OAuth handles delegated authorization and OpenID Connect standardizes authentication on top of it.

Redraw it, then practice the follow-ups

Practice drawing the nine steps in under a minute, labeling which arrows pass through the browser. Then prepare short answers to the follow-ups interviewers like: why the code is single-use, what state protects against, why a single-page app is a public client, and what changes when a backend-for-frontend keeps tokens on the server. RFC 9700 lets a client rely on PKCE for CSRF protection once it has confirmed that the server supports PKCE; otherwise state must carry a one-time CSRF token bound to the browser.

Then rehearse the decision in reverse: given a smart TV app, a nightly batch job, and a mobile app, name the grant for each and the reason: the device grant, client credentials, and authorization code with PKCE. Recompute the RFC 7636 test vector from memory as well; it quickly shows whether you remember base64url without padding. The question sets below cover the surrounding authentication and HTTP material.

Quick answers

Frequently asked questions

Is PKCE only for mobile apps and single-page apps?

No. RFC 9700 requires PKCE for public clients and recommends it for confidential ones, because it also stops authorization code injection against server-side apps. It costs one random value and one hash per login, so there is little reason to skip it for a server-side client.

Can a single-page app keep a client secret?

No. Anything shipped in JavaScript can be read from the bundle or the network panel, so a single-page app is a public client. It should use authorization code with PKCE, or hand token handling to a backend-for-frontend that is a confidential client and keeps tokens out of the browser.

Is the client credentials flow just an API key?

Both identify a calling application, but client credentials exchange a long-term secret for short-lived, scoped access tokens issued by an authorization server. The API only ever sees the token, which can expire quickly and carry scopes, while the client secret travels only to the token endpoint.

Does OAuth 2.1 replace OAuth 2.0 today?

Not formally. As of November 2026 OAuth 2.1 is an Internet-Draft. The practices it collects, such as PKCE, exact redirect URI matching, and dropping the implicit and password grants, are already recommended for OAuth 2.0 deployments by RFC 9700.

Source notes

References and review policy

Information checked on November 7, 2026. Section links identify sources for factual claims and technical explanations. Interpretations, practice scenarios and preparation recommendations are RecallDeck’s editorial work.

From reading to recall

Practice the full interview loop.

RecallDeck schedules the concepts you miss and keeps coding, design, and behavioral fundamentals available when the interviewer changes direction.

Start studying

Keep going