Mermaid code
sequenceDiagram
participant C as Client
participant A as Auth API
participant R as Resource API
C->>A: POST /login with email and password
A->>A: Verify password hash
A-->>C: access_token (15 min) and refresh_token (30 days)
C->>R: GET /orders with Bearer access_token
R->>R: Verify signature and exp claim
alt Token valid
R-->>C: 200 orders
else Token expired
R-->>C: 401 token_expired
C->>A: POST /refresh with refresh_token
alt Refresh token valid
A-->>C: New access_token
C->>R: Retry GET /orders
R-->>C: 200 orders
else Refresh revoked or expired
A-->>C: 401 reauthenticate
C->>C: Redirect to login
end
end Paste this into any tool that renders Mermaid, or into the editor to change the theme and export PNG or SVG. Syntax reference: Sequence Diagram guide.
What this diagram shows
Three lanes: Client, Auth API, and Resource API. Login returns two tokens with their lifetimes written into the message. The resource API verifies the signature and the exp claim locally, with no call to the auth API, which is the main reason to use JWTs at all. The outer alt splits on token validity. Inside the expired branch, the nested alt splits again on whether the refresh token is still good: a new access token and a transparent retry, or a hard 401 that sends the user back to login.
How to adapt it
- Change the lifetimes in the first response to your real values; the diagram is a good place to make the 15-minute and 30-day choice explicit.
- Add
A->>A: Rotate refresh tokenbefore issuing the new access token if you use refresh token rotation, and extend the else branch with the reuse-detection case. - Replace
GET /orderswith any protected call; keep the retry line so readers see that the client, not the user, handles expiry. - Add a
Note over R: Public key cached from /jwksto show where the verification key comes from.
FAQ
Why does the resource API not call the auth API?
Because the JWT is self-contained: the signature and the exp claim are enough to accept or reject it. That is the trade-off against sessions, where every request would hit a store. Revocation before expiry needs a separate mechanism such as a short access token lifetime or a denylist.
How do I show the retry only happening once?
Sequence diagrams do not express counters, so write it in the message or a note: C->>R: Retry GET /orders (once). If retries loop, use a loop block with the limit in its label.