OAuth 2.0 / OIDC Sequence Diagram

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

OIDC Authorization Code Flow with PKCE User browser Client app web backend Authorization server (IdP) Resource API orders 1 click Sign in 2 create code_verifier (random 43-128 chars), code_challenge = BASE64URL(SHA256(verifier)), state, nonce; store in session 3 302 redirect to /authorize 4 GET /authorize?response_type=code&client_id&redirect_uri &scope=openid profile&state&nonce&code_challenge&code_challenge_method=S256 5 login page (password + MFA), consent screen 6 credentials, approve scopes 7 store code_challenge with one-time code 8 302 redirect_uri?code=SplxlOBe&state=af0ifjsl 9 GET /callback?code&state 10 check state equals session value 11 POST /token grant_type=authorization_code code, redirect_uri, code_verifier, client auth 12 BASE64URL(SHA256(code_verifier)) == stored code_challenge ? 13 200 {access_token, id_token, refresh_token} 14 validate ID token: signature via JWKS, iss, aud = client_id, exp, nonce 15 GET /orders Authorization: Bearer access_token 16 verify JWT aud, scope, exp 17 200 JSON orders 18 session cookie + page

Step by step:

  1. 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).
  2. The browser is redirected to the authorization server's /authorize endpoint with only the challenge. The verifier never travels through the browser.
  3. 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.
  4. The code comes back on the front channel (step 8). The client first checks that state matches what it stored.
  5. 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.
  6. On success it returns an access token (for APIs), an ID token (a signed JWT describing the user) and optionally a refresh token.
  7. 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.
  8. 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

Sequence Diagram Notation Used Participant lifeline request synchronous message reply reply message self message activation alt [guard] [else] combined fragment (alt)

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

Error Paths: PKCE Stops a Stolen Code Client app has verifier Authorization server Attacker stole the code alt [attacker redeems code first] POST /token code=SplxlOBe, no code_verifier 400 {"error": "invalid_grant"} [else: legitimate client] POST /token code, code_verifier 200 tokens Other rejections the client must handle state mismatch on /callback: abort, possible CSRF | nonce mismatch in ID token: replayed token code reused: server returns invalid_grant and should revoke tokens already issued for that code redirect_uri not exactly registered: server shows an error and never redirects

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

Cons

When to use

When not to use

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