Tool · OAuth 2.0
OAuth flow visualiser
Step through the authorization code flow with PKCE, the client credentials grant and the device authorization grant, one HTTP message at a time, with what each step protects against.
Every step, message by message
The same flows written out in full. PKCE values here are the published example from RFC 7636 Appendix B; the interactive version above generates fresh ones in your browser. Host names are examples. Line breaks inside URLs and form bodies are for display only.
Authorization code + PKCE
Any client that acts for a signed-in user: web apps, single-page apps, mobile and desktop apps. The default choice (RFC 9700 §2.1.1).
User clicks “Connect” (Browser (user) → Client app)
GET /connect HTTP/1.1 Host: client.example.orgThe user asks the client to do something that needs their data at the API. RFC 6749 §1.2
Make verifier, challenge, state (Client app)
code_verifier = dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk code_challenge = BASE64URL(SHA-256(code_verifier)) = E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM state = af0ifjsldkjThe verifier is a high-entropy random string (43–128 characters) kept on the client. Only its SHA-256 hash travels in the browser. state is a one-time value bound to this browser session. Risk: Never reuse a verifier or send method 'plain': S256 is the only method that keeps the verifier out of the authorization request (RFC 9700 §2.1.1). RFC 7636 §4.1–4.2
302 to the authorization endpoint (Client app → Browser (user))
HTTP/1.1 302 Found Location: https://as.example.com/authorize ?response_type=code &client_id=s6BhdRkqt3 &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb &scope=openid%20orders.read &state=af0ifjsldkj &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM &code_challenge_method=S256Line breaks are for display only. The request asks for a code (not a token), names the registered redirect URI and the scopes, and carries the PKCE challenge. RFC 6749 §4.1.1
GET /authorize (Browser (user) → Authorization server)
GET /authorize?response_type=code&client_id=s6BhdRkqt3&… HTTP/1.1 Host: as.example.comThe authorization server checks client_id and compares redirect_uri to the registered value by exact string match. Risk: Pattern or prefix matching of redirect URIs lets an attacker steer the code to a URL they control (RFC 9700 §4.1). RFC 9700 §2.1
Sign in and consent (Authorization server → Browser (user))
(login page, MFA, consent screen: “client.example.org wants to read your orders”)The user authenticates to the authorization server, never to the client: the client never sees the password. The consent screen lists what the scopes allow. RFC 6749 §4.1 (B)
302 back with code (Authorization server → Browser (user))
HTTP/1.1 302 Found Location: https://client.example.org/cb ?code=SplxlOBeZQQYbYS6WxSbIA &state=af0ifjsldkj &iss=https%3A%2F%2Fas.example.comThe code is short-lived and single-use. The iss parameter (RFC 9207) tells the client which authorization server answered, a defence against mix-up attacks when a client talks to several. Risk: The code passes through the browser, its history and possibly logs. On its own it is useless: redeeming it needs the code_verifier. RFC 6749 §4.1.2
Browser delivers the code (Browser (user) → Client app)
GET /cb?code=SplxlOBeZQQYbYS6WxSbIA&state=af0ifjsldkj&iss=https%3A%2F%2Fas.example.com HTTP/1.1 Host: client.example.orgThe client checks that state matches the value it stored for this browser session, and that iss is the server it sent the user to. Risk: Skipping the state (or PKCE) check allows CSRF: an attacker logs the victim into the attacker's account (RFC 9700 §4.7). RFC 9700 §4.7
POST /token with code_verifier (Client app → Authorization server)
POST /token HTTP/1.1 Host: as.example.com Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=SplxlOBeZQQYbYS6WxSbIA &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb &client_id=s6BhdRkqt3 &code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXkA back-channel request, client to server. A confidential client also authenticates here (client secret, private_key_jwt or mTLS); a public client cannot hold a secret, so PKCE is what protects it. RFC 7636 §4.5
Check verifier against challenge (Authorization server)
BASE64URL(SHA-256("dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk")) == "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM" ✓ code unused, unexpired, issued to s6BhdRkqt3 ✓ redirect_uri identical to the authorization request ✓Only the party that started the flow knows the verifier, so a stolen or injected code cannot be redeemed by anyone else. Risk: Servers MUST reject a code_verifier when the authorization request had no code_challenge, or attackers can downgrade (RFC 9700 §4.8). RFC 7636 §4.6
Tokens (Authorization server → Client app)
HTTP/1.1 200 OK Content-Type: application/json Cache-Control: no-store { "access_token": "2YotnFZFEjr1zCsicMWpAA", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA", "id_token": "eyJhbGciOiJSUzI1NiIs…", "scope": "openid orders.read" }An ID token appears because the openid scope made this an OpenID Connect request: it tells the client who signed in. The access token is for the API. Risk: Refresh tokens for public clients must be sender-constrained or rotated on every use (RFC 9700 §4.14.2). RFC 6749 §5.1
Call the API with the token (Client app → Resource server (API))
GET /orders HTTP/1.1 Host: api.example.com Authorization: Bearer 2YotnFZFEjr1zCsicMWpAABearer tokens go in the Authorization header, not in the URL. Risk: Anyone holding a bearer token can use it. DPoP or mTLS binding makes a stolen token useless without the client's key (RFC 9700 §2.2). RFC 6750 §2.1
200 OK (Resource server (API) → Client app)
HTTP/1.1 200 OK Content-Type: application/json [{"order": 1042, "status": "shipped"}]Before answering, the API validates the token (signature or introspection, issuer, audience, expiry) and checks the scope covers this request. RFC 9068 §4
Client credentials
Machine-to-machine calls where no user is involved: a back-end job calling an internal API. The client acts on its own behalf.
POST /token, client authenticates (Client (service) → Authorization server)
POST /token HTTP/1.1 Host: as.example.com Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3 Content-Type: application/x-www-form-urlencoded grant_type=client_credentials &scope=reports.writeThe client proves its identity directly. Basic authentication with a shared secret is shown; private_key_jwt (RFC 7523) or mutual TLS (RFC 8705) avoid sending a reusable secret. Risk: A client secret in source code, CI logs or a container image is a standing credential for everything the client can reach. RFC 6749 §4.4.2
Authenticate client, check scope (Authorization server)
client s6BhdRkqt3 authenticated ✓ scope reports.write allowed for this client ✓The grant is only for confidential clients: anyone could otherwise request tokens as the client. RFC 6749 §4.4
Access token (no refresh token) (Authorization server → Client (service))
HTTP/1.1 200 OK Content-Type: application/json Cache-Control: no-store { "access_token": "2YotnFZFEjr1zCsicMWpAA", "token_type": "Bearer", "expires_in": 3600 }A refresh token SHOULD NOT be included: the client can simply authenticate again. Cache the token until shortly before it expires instead of fetching one per call. RFC 6749 §4.4.3
Call the API (Client (service) → Resource server (API))
POST /reports HTTP/1.1 Host: api.example.com Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA Content-Type: application/json {"period": "2026-Q3"}The API sees a token whose subject is the client itself, not a user. RFC 6750 §2.1
201 Created (Resource server (API) → Client (service))
HTTP/1.1 201 Created Location: /reports/77Scope the client to exactly the APIs and operations it needs: a service identity is a privileged account like any other. SP 800-53 AC-6
Device authorization
Devices with no browser or no easy keyboard: TVs, CLI tools, consoles. The user approves on a second device.
POST /device_authorization (Device (TV / CLI) → Authorization server)
POST /device_authorization HTTP/1.1 Host: server.example.com Content-Type: application/x-www-form-urlencoded client_id=1406020730&scope=example_scopeThe device asks for a pair of codes. It should start only when the user asks, not automatically. RFC 8628 §3.1
device_code + user_code (Authorization server → Device (TV / CLI))
HTTP/1.1 200 OK Content-Type: application/json Cache-Control: no-store { "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", "user_code": "WDJB-MJHT", "verification_uri": "https://example.com/device", "verification_uri_complete": "https://example.com/device?user_code=WDJB-MJHT", "expires_in": 1800, "interval": 5 }The device_code stays on the device; the short user_code is for the human. Values from the RFC's own example. RFC 8628 §3.2
Show code and URL (Device (TV / CLI) → User's phone or laptop)
To sign in, visit example.com/device and enter WDJB-MJHT (or scan the QR code)The user reads the code off the screen and continues on a phone or laptop. Risk: Attackers can start a device flow themselves and send the victim the code with a pretext (“enter this code to view the document”). RFC 8628 §5.4 recommends telling the user they are authorizing a device and asking them to confirm it is theirs. RFC 8628 §3.3
Sign in, enter code, approve (User's phone or laptop → Authorization server)
GET /device HTTP/1.1 Host: example.com (sign in, MFA, enter WDJB-MJHT, approve access)Authentication happens on a device with a full browser, so phishing-resistant methods like passkeys still work. RFC 8628 §3.3
Poll /token (Device (TV / CLI) → Authorization server)
POST /token HTTP/1.1 Host: server.example.com Content-Type: application/x-www-form-urlencoded grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code &device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS &client_id=1406020730Meanwhile the device polls, waiting at least `interval` seconds between requests. RFC 8628 §3.4
authorization_pending (Authorization server → Device (TV / CLI))
HTTP/1.1 400 Bad Request Content-Type: application/json Cache-Control: no-store {"error": "authorization_pending"}Not yet approved: keep polling. slow_down means add 5 seconds to the interval for this and every later request; access_denied and expired_token end the flow. RFC 8628 §3.5
Tokens, once approved (Authorization server → Device (TV / CLI))
HTTP/1.1 200 OK Content-Type: application/json Cache-Control: no-store { "access_token": "2YotnFZFEjr1zCsicMWpAA", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA" }The next poll after approval returns a normal token response. RFC 8628 §3.5
Call the API (Device (TV / CLI) → Resource server (API))
GET /me/library HTTP/1.1 Host: api.example.com Authorization: Bearer 2YotnFZFEjr1zCsicMWpAAFrom here it is ordinary OAuth. RFC 6750 §2.1
Which flow to use
| Situation | Flow | Why |
|---|---|---|
| A user signs in to a web app, single-page app, mobile or desktop app | Authorization code + PKCE | Tokens never pass through the browser's URL; PKCE stops stolen or injected codes. RFC 9700 requires PKCE for public clients and recommends it for confidential ones. |
| A service calls an API on its own behalf | Client credentials | No user is involved. Use a strong client authentication method and narrow scopes. |
| A TV, console or command-line tool with no usable browser | Device authorization | The user approves on a phone or laptop with a full browser. |
| Anything still using the implicit grant | Move to code + PKCE | RFC 9700 §2.1.2: clients SHOULD NOT use the implicit grant, which puts access tokens in the URL. |
| Apps collecting the user's password to exchange for tokens | Move to code + PKCE | RFC 9700 §2.4: the resource owner password credentials grant MUST NOT be used. |
Sources
- RFC 6749: The OAuth 2.0 Authorization Framework, IETF. IETF Trust Legal Provisions (brief quotations with attribution).
- RFC 7636: Proof Key for Code Exchange by OAuth Public Clients, IETF. IETF Trust Legal Provisions (brief quotations with attribution).
- RFC 8628: OAuth 2.0 Device Authorization Grant, IETF. IETF Trust Legal Provisions (brief quotations with attribution).
- RFC 9700: Best Current Practice for OAuth 2.0 Security, IETF. IETF Trust Legal Provisions (brief quotations with attribution).
- RFC 9207: OAuth 2.0 Authorization Server Issuer Identification, IETF. IETF Trust Legal Provisions (brief quotations with attribution).
- RFC 6750: The OAuth 2.0 Authorization Framework: Bearer Token Usage, IETF. IETF Trust Legal Provisions (brief quotations with attribution).