This is the OAuth 2.1 Authorization Code flow with PKCE, the standard way a browser or mobile app gets access to an API on a user's behalf without ever handling the user's password. It is drawn here as a classic 2D message-sequence diagram, top-to-bottom.
code_verifier = random 43-128 char string (this sim: 32 random bytes, base64url)
code_challenge = BASE64URL( SHA-256( code_verifier ) )
...at /authorize the client sends code_challenge only.
...at /token it proves possession by sending code_verifier.
Server check: BASE64URL(SHA256(received_verifier)) == stored_challenge ?
- Step / Play / Reset — walk through the message exchange between Browser, Client App, Authorization Server and Resource Server, drawn as arrows down a sequence diagram.
- Enable PKCE — toggles whether the Authorization Server enforces the verifier↔challenge check at the token endpoint. Turning it off reproduces the pre-2019 Authorization Code flow that PKCE was designed to replace.
- Simulate a stolen authorization code — a malicious app on the same device intercepts the redirect that carries the one-time code (a real risk on mobile, where multiple apps can register the same custom URI scheme) and tries to exchange it for tokens itself.
- With PKCE on, the attacker's request is rejected because it cannot supply the original
code_verifier — only a SHA-256 preimage match is accepted. With PKCE off, the bare code is enough and the attacker walks away with a valid access token, which is exactly the class of attack RFC 7636 closed.
- Fix applied in this 2D version: authorization codes are single-use per RFC 6749 §4.1.2. If PKCE is off and the attacker redeems the stolen code first, the code is consumed — so the legitimate client's own token exchange is then correctly shown failing too (
invalid_grant), not silently succeeding as if nothing happened. The original 3D engine this sim is paired with always let the legitimate exchange succeed regardless of what the attacker did; that's fixed here only, not in the 3D source.
- OpenID Connect layers an
id_token (a signed JWT: header.payload.signature) on top of the same flow to additionally authenticate who the user is, not just authorize API access.