← Back to Blog
System Design

OAuth 2.0 PKCE Sequence Diagram: Authorization Code Flow, code_verifier and Token Exchange

An OAuth PKCE sequence diagram has four lifelines and one value that never rides a browser redirect. The client app creates a code_verifier, sends only a derived code_challenge through the browser, and later presents the original code_verifier on a direct call to the token endpoint. The authorization code grant is defined in RFC 6749, Proof Key for Code Exchange (PKCE) is defined in RFC 7636, and RFC 9700, the Best Current Practice for OAuth 2.0 Security, says public clients must use PKCE. The detail diagram below draws the exchange as ten numbered steps, from verifier creation to the API call.

OAuth 2.0 PKCE Sequence Diagram: Authorization Code Flow, code_verifier and Token Exchange
Four lifelines run left to right: User + browser (user agent), Client app (holds code_verifier), Authorization server (authorize + token endpoints) and Resource server (API). Each lifeline is a thin dashed vertical line; it marks time, not a message. Dashed arrows are front-channel browser redirects, and solid arrows are back-channel direct HTTPS calls. Steps 1 and 2 are notes on the Client app: create a random code_verifier of 43 to 128 characters kept on the client, then code_challenge = BASE64URL(SHA256(code_verifier)). Step 3 has two arrows: a 302 redirect to the authorization endpoint, then GET /authorize with response_type=code, client_id, redirect_uri, scope, state, code_challenge and code_challenge_method=S256. Step 4 is authenticate the user + get consent, and step 5 stores code_challenge with the code. Step 6 has two arrows: a 302 to redirect_uri with code + state, then code + state from the browser to the Client app. Step 7 checks that state matches this session. Step 8 is POST /token with grant_type=authorization_code, code, redirect_uri, client_id and code_verifier. Step 9 compares S256(code_verifier) with the stored code_challenge: a match issues tokens, and no match returns invalid_grant. Step 10 has two arrows: access_token (+ optional refresh_token) back to the Client app, then an API call with Authorization: Bearer access_token.
OAuth 2.0 PKCE sequence diagram cover: client app keeps code_verifier, code_challenge through the browser, code_verifier on the back-channel token call
Listing cover: the Client app keeps code_verifier. Dashed front-channel arrows carry code_challenge with method S256 through the User + browser to the Authorization server and bring the code back to the browser by redirect. A solid back-channel arrow carries code + code_verifier to the Authorization server, which compares S256(verifier). Further solid arrows return the access_token to the Client app and carry Bearer access_token to Your API on the resource server.

What this OAuth PKCE sequence diagram shows

RFC 6749 defines four roles: the resource owner, the resource server, the client, and the authorization server. In the authorization code grant the resource owner interacts through a user-agent, so the diagram uses four lifelines from left to right: User + browser, Client app, Authorization server, and Resource server. RFC 6749 section 4.1 describes the grant in steps (A) through (E). The client directs the user-agent to the authorization endpoint, the authorization server authenticates the resource owner and establishes whether access is granted, the server redirects the user-agent back with an authorization code, the client requests an access token from the token endpoint with that code, and the server validates the request and returns an access token and, optionally, a refresh token.

PKCE adds three things to that sequence. A code_verifier is created before the authorization request and stays with the client. A derived code_challenge and its code_challenge_method travel on the authorization request. The original code_verifier travels on the token request, and the authorization server recomputes the challenge before it issues tokens. The detail diagram numbers ten steps. Steps 3, 6 and 10 each have two arrows: the redirect and the browser's GET to /authorize, the redirect back and the browser handing code and state to the client, and the token response followed by the API call.

The problem PKCE solves

The authorization code returns to the client inside a redirect, so anything that can read that redirect can try to redeem the code. RFC 7636 describes the attack against public clients on native platforms: a malicious application registers the same custom URI scheme as the legitimate app, receives the redirect, and exchanges the intercepted code for an access token. A public client has no client secret to prove its identity at the token endpoint, so the code alone is enough.

PKCE binds the code to a secret that only the client instance that started the flow knows. An attacker who intercepts the authorization response sees the code, and may have seen the challenge, but not the verifier, so the token request fails. RFC 9700 widens the scope beyond native apps. It says that although PKCE was designed as a mechanism to protect native apps, its advice applies to all kinds of OAuth clients, including web applications, and it describes PKCE as a countermeasure to authorization code injection, where an attacker tries to plant a code in a victim's session. RFC 9700 says public clients must use PKCE, recommends it for confidential clients, and says authorization servers must support it.

Lifelines and trust boundaries

Front channel. Messages that travel as browser redirects pass through the user-agent. On the detail diagram these arrows are dashed: both arrows of step 3, the step 4 authentication and consent arrow, and both arrows of step 6. Only values that may be exposed belong here: response_type, client_id, redirect_uri, scope, state, code_challenge, code_challenge_method, and later the authorization code.

Back channel. The token request is a direct call from the client to the token endpoint. RFC 6749 says the client sends its parameters using the application/x-www-form-urlencoded format in the HTTP request body, and a confidential client authenticates with the authorization server here. The code_verifier is sent on this call. On the detail diagram the back-channel arrows are solid: the step 8 POST /token and both arrows of step 10.

Endpoints. The Authorization server lifeline is labeled authorize + token endpoints, and the arrows name them GET /authorize and POST /token. RFC 6749 says the location of the authorization endpoint is typically provided in the service documentation, and its own examples use those paths. The Client app lifeline is labeled holds code_verifier, because that value stays with the client until the token request.

The flow, step by step

  1. Create code_verifier. RFC 7636 defines the code_verifier as a high-entropy cryptographic random string using the unreserved characters A to Z, a to z, 0 to 9, hyphen, period, underscore and tilde, with a minimum length of 43 characters and a maximum length of 128. It recommends generating a 32-octet random sequence and base64url-encoding it, which produces a 43-octet string. The diagram's note reads 43 to 128 characters, kept on the client.
  2. Derive code_challenge. With the S256 method, code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier))). RFC 7636 says a client capable of using S256 must use it, because S256 is mandatory to implement on the server. The plain method is permitted only when the client cannot support S256 for a technical reason and knows the server supports plain.
  3. Redirect to the authorization endpoint. The client answers with a 302 redirect to the authorization endpoint, and the browser sends GET /authorize with response_type=code, client_id, redirect_uri, scope, state, code_challenge, and code_challenge_method=S256.
  4. Authenticate user and consent. The authorization server authenticates the resource owner through the user-agent and establishes whether access is granted. RFC 6749 says the way the authorization server authenticates the resource owner is beyond the scope of the specification.
  5. Store the challenge with the code. RFC 7636 says that when the server issues the authorization code, it must associate the code_challenge and code_challenge_method values with that code so they can be verified later. Typically they are stored in encrypted form in the code itself, but they could be stored on the server. RFC 6749 recommends a maximum authorization code lifetime of 10 minutes and says the client must not use a code more than once.
  6. Redirect back with the code. The authorization server sends a 302 to redirect_uri with code and the same state, and the browser hands both to the client. RFC 9700 says authorization servers must use exact string matching when comparing client redirection URIs against pre-registered URIs, except for port numbers in localhost redirection URIs of native apps.
  7. Check state. The client checks that state matches the session that started the flow. RFC 9700 says a client that can interact with more than one authorization server requires a defense against mix-up attacks, and it should use the iss parameter for that purpose.
  8. Exchange the code. The client sends POST /token with grant_type=authorization_code, code, redirect_uri, client_id, and code_verifier. RFC 6749 requires the redirect_uri value to be identical to the one in the authorization request when that request included it. A confidential client also authenticates.
  9. Verify the proof. The token endpoint transforms the received code_verifier with the recorded method and compares the result with the stored code_challenge. RFC 7636 says that if the values are equal, processing continues, and if they are not equal, an invalid_grant error must be returned. The step 9 box shows those two outcomes. RFC 6749 adds that a code used more than once must be denied, and RFC 9700 adds that a token request containing a code_verifier is accepted only if a code_challenge was present in the authorization request.
  10. Return tokens and call the API. The token response carries an access token and, optionally, a refresh token, as RFC 6749 section 4.1.4 describes. The second arrow of step 10 is the API call to the resource server with the access token in an Authorization: Bearer header, the form shown in the RFC 6749 access token example.

code_verifier, code_challenge and S256 in detail

The two values have different jobs. The code_verifier is the secret, and it is created per authorization request. The code_challenge is what the authorization server records with the code. With plain, the challenge equals the verifier, so anyone who reads the authorization request learns the secret. With S256, only the base64url-encoded SHA-256 hash travels on the front channel. RFC 9700 says clients should use PKCE code challenge methods that do not expose the verifier in the authorization request, and that S256 is currently the only such method.

RFC 7636 Appendix B gives a worked S256 example. Its 32 random octets encode to the verifier dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk, and the base64url-encoded SHA-256 hash of that string is the challenge E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM. Combined with the client identifier, state, redirect URI and code used in the RFC 6749 examples, the authorization request and the token request look like this:

GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
    &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
    &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
    &code_challenge_method=S256
Host: server.example.com

POST /token
Host: server.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
&client_id=s6BhdRkqt3
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

The verifier appears only in the second request. On the detail diagram, the only arrow labeled with code_verifier is step 8, a solid back-channel arrow. The name also appears in the Client app header and in the notes for steps 1, 2 and 9, none of which is a front-channel arrow.

Common mistakes the diagram makes visible

  • A verifier on a dashed arrow. If code_verifier ever appears on a front-channel arrow, the secret has passed through the browser and PKCE no longer separates the legitimate client from an interceptor.
  • The plain method by default. RFC 7636 limits plain to clients that cannot support S256 for a technical reason, and RFC 9700 notes that S256 is the only method that does not expose the verifier.
  • A server that ignores a missing challenge. RFC 9700 section 4.8 describes a PKCE downgrade attack. The attacker starts a flow on their own device, removes the code_challenge from the authorization request, and obtains a code that is not bound to any challenge. The attacker then sends the victim's browser to an authorization response containing that code, and the victim's client redeems it with its own verifier. A server that does not check the verifier for an unbound code issues a token for the attacker's resource. The countermeasure is the RFC 9700 rule that a token request containing a code_verifier is accepted only if a code_challenge was present in the authorization request. The diagram draws the normal comparison in step 9, and this rule is an additional check at the same point.
  • Tokens in the front channel. RFC 9700 says clients should not use the implicit grant (response type token) or other response types that issue access tokens in the authorization response, unless access token injection is prevented and the leakage vectors are mitigated. In this diagram, tokens appear only on solid arrows.
  • A password arrow. RFC 9700 says the resource owner password credentials grant must not be used. No arrow in the diagram has the client collecting the user's password.
  • A reusable code. RFC 6749 says the client must not use a code more than once, and that the authorization server must deny a request that reuses one.

What the RFCs do not say

The three RFCs do not set an access token or refresh token lifetime for you. The only recommended lifetime among them is for the authorization code, which RFC 6749 states as a recommended maximum of 10 minutes. The expires_in value of 3600 in its token response example is only an illustration. RFC 6749 also leaves user authentication out of scope, so the diagram does not show passwords, passkeys or federation inside step 4.

RFC 7636 says the exact method a server uses to associate the challenge with the code is out of scope, and it describes how the verifier is created and used rather than prescribing a client storage mechanism. The endpoint paths on the diagram follow the RFC examples, while real deployments publish their own locations.

FAQ

Is PKCE only for mobile apps?

No. RFC 7636 was designed to protect native apps, but RFC 9700 says the advice applies to all kinds of OAuth clients, including web applications. Public clients must use PKCE, and PKCE is recommended for confidential clients.

Why is S256 preferred over the plain method?

With plain, the code_challenge equals the code_verifier, so anyone who reads the authorization request learns the secret. With S256 only the base64url-encoded SHA-256 hash is sent. RFC 7636 says a client capable of S256 must use it, and RFC 9700 says S256 is currently the only method that does not expose the verifier in the authorization request.

Does PKCE replace the state parameter?

Partly. RFC 9700 says clients that have ensured the authorization server supports PKCE may rely on the CSRF protection PKCE provides. In OpenID Connect flows the nonce parameter provides CSRF protection. Otherwise, one-time CSRF tokens carried in the state parameter and securely bound to the user agent must be used.

Conclusion

The OAuth PKCE sequence diagram is four lifelines and ten numbered steps. The client creates a code_verifier and derives an S256 code_challenge, the challenge travels through the browser to the authorization endpoint, the server authenticates the user and stores the challenge with a short-lived, single-use code, and the code returns by redirect with state. The client then redeems the code with the code_verifier on the back channel, the token endpoint compares the hash with the stored challenge, and tokens come back before the Bearer call to the API. RFC 6749 defines the grant, RFC 7636 defines the proof, and RFC 9700 makes PKCE the expected baseline, requires servers to block the PKCE downgrade, says clients should not use the implicit grant, and forbids the password grant. Dashed front-channel arrows and solid back-channel arrows make the security argument visible in one picture.

Animate your OAuth sequence diagram

Lay out the browser, client, authorization server, and API lifelines, then step through each redirect and the PKCE token exchange.

Open Diagram Editor