This UML sequence diagram shows how a web application signs a user in with OpenID Connect on top of the OAuth 2.0 authorization code flow with PKCE (RFC 7636). Time runs downward; each arrow is an HTTP request, redirect or local check. The diagram makes clear which party holds which secret at each step, which is exactly what a security review needs to see.
1. The Diagram
Step by step:
Before anything leaves the client it creates a random code_verifier and derives code_challenge from it with SHA-256. It also creates state (CSRF protection) and nonce (ID-token replay protection).
The browser is redirected to the authorization server's /authorize endpoint with only the challenge. The verifier never travels through the browser.
The user authenticates (password plus MFA) and consents to the openid profile scopes. The server stores the challenge with a short-lived, one-time code.
The code comes back on the front channel (step 8). The client first checks that state matches what it stored.
On the back channel (step 11, yellow) the client posts the code together with the original code_verifier. The server hashes it and compares with the stored challenge.
On success it returns an access token (for APIs), an ID token (a signed JWT describing the user) and optionally a refresh token.
The client validates the ID token: signature against the issuer's JWKS keys, iss, aud, exp and nonce. Only then is the user logged in.
The access token is sent as a Bearer token to the resource API, which checks audience, scope and expiry before answering.
2. How to Read It
Solid arrows with filled heads are requests; dashed arrows with open heads are replies (here, mostly HTTP 302 redirects and 200 responses). A loop on one lifeline is a local computation. Thin bars show when a participant is busy. Colours on this page are only emphasis: yellow for the PKCE proof, green for success, red for rejection.
3. Variant: Error Paths with an alt Fragment
The second diagram uses a UML alt combined fragment to show why PKCE exists. A malicious app on the same device, or a leaked log line, can capture the authorization code from the redirect. Without the verifier the code is useless, so the token endpoint answers invalid_grant. The red box lists the other checks that are easy to forget and should each have a test.
4. Pros, Cons, and When to Use
Pros
Shows the exact order of redirects, back-channel calls and checks.
Makes it obvious which secrets cross the browser and which stay server-side.
Each arrow maps to a request you can capture in a proxy and test.
alt and opt fragments document error handling precisely.
Widely understood by developers and auditors.
Cons
Gets tall fast; parameters must be abbreviated to fit.
Shows one scenario; other grant types need their own diagrams.
Says nothing about where components are deployed or which networks they sit on.
Easy to draw a flow that looks right but omits a validation step.
When to use
Authentication, token exchange and any multi-party protocol.
Reviewing a third-party integration or SSO rollout.
Explaining an incident timeline across services.
When not to use
Static structure or network layout (use a deployment or topology diagram).
Long-running workflows with many branches (use an activity or BPMN diagram).
5. Diagrams-as-Code
PlantUML version of the main diagram:
@startuml
actor User
participant "Client app" as C
participant "Authorization server" as AS
participant "Resource API" as API
User -> C : click Sign in
C -> C : code_verifier, code_challenge = BASE64URL(SHA256(verifier)), state, nonce
C --> User : 302 /authorize
User -> AS : GET /authorize?response_type=code&client_id&redirect_uri\n&scope=openid profile&state&nonce&code_challenge&code_challenge_method=S256
AS --> User : login + MFA, consent
User -> AS : credentials, approve
AS --> User : 302 redirect_uri?code&state
User -> C : GET /callback?code&state
C -> C : check state
C -> AS : POST /token (code, redirect_uri, code_verifier)
AS -> AS : SHA256(verifier) == challenge?
AS --> C : access_token, id_token, refresh_token
C -> C : validate id_token (JWKS sig, iss, aud, exp, nonce)
C -> API : GET /orders (Bearer access_token)
API --> C : 200 JSON
C --> User : session cookie + page
@enduml