Authentication service architecture.
Stateless tokens make verification free and revocation hard, and that single tradeoff explains most of the components in this diagram.
The components
Every row below is read from the graph that produced the diagram above, so the two cannot disagree.
| Component | Tier | Why it is there |
|---|---|---|
| Client app | Client | Supporting component |
| API gateway | Edge | Verifies the access token on every request |
| Auth service | Application | Supporting component |
| OAuth / OIDC | Application | Authorization code with PKCE |
| MFA service | Application | Supporting component |
| Session service | Application | Refresh token rotation, reuse detection |
| Key service | Application | Rotating signing keys, published as JWKS |
| Audit log | Application | Append only, separate from application data |
| Postgres | Data | Argon2 hashes, never reversible |
| Redis | Data | Revocation list, since JWTs cannot be recalled |
| External IdPs | External | Supporting component |
| Email provider | External | Supporting component |
Design decisions worth arguing about
A diagram shows what was chosen. It does not show what it cost, and that is usually the part that matters in a review or an interview.
Stateless access tokens, and the revocation problem they create
A signed JWT lets the gateway verify a request with no network call, which is why the pattern is everywhere. The consequence is that you cannot un-issue one: a token stolen or a user disabled remains valid until it expires. Every mitigation is a way of buying back some statefulness, and the honest version is short expiry plus a revocation list checked for the cases that matter, which reintroduces exactly the lookup the design was avoiding.
Short access tokens, rotating refresh tokens
Access tokens measured in minutes keep the revocation window small; refresh tokens measured in weeks keep users logged in. Rotating the refresh token on every use adds theft detection almost for free: if an old refresh token is presented again, either it was replayed or a legitimate client raced, and either way the family should be invalidated. The cost is state per token family and occasional logouts for clients that genuinely raced.
Key rotation, published rather than distributed
Signing keys have to rotate, and pushing new keys to every verifier is a coordination problem you do not want during an incident. Publishing a JWKS endpoint and letting verifiers fetch and cache turns rotation into a deploy of one service. It makes that endpoint availability-critical for the whole system, which is a real cost and the reason it should be cached aggressively with a stale fallback.
PKCE everywhere, not only on mobile
The authorization code flow with PKCE was introduced for clients that cannot keep a secret, and it is now the right default for confidential clients too, because it closes code interception regardless of client type. It costs a little client complexity and removes a class of attack entirely, which is a trade worth making by default rather than by threat model.
How it changes with scale
Login volume is small relative to verification volume, and verification is where the design pays off: a stateless check scales with your services rather than with this one. What grows is the session store, which holds refresh state and revocations, and the audit log, which is append-only and retained for compliance rather than for utility.
Where it breaks first
Clock skew, which is invisible until it is not. Token validity is time-bounded, so a verifier whose clock drifts rejects valid tokens or accepts expired ones, and the symptom is intermittent authentication failures on a subset of hosts with nothing obviously wrong. Anyone who has debugged it once puts NTP monitoring in place afterwards.
Draw this yourself
Open the Diagram tab and describe the system. The agent emits a semantic graph rather than coordinates, so you can edit the components and the layout re-solves instead of drifting.
When the shape is right, Implement in code turns the canvas into a markdown specification, every component, every relationship and the notes, and starts a real turn in the Code tab with it.
Questions about this design
More templates
- URL Shortener System DesignThe canonical read-heavy system: roughly a hundred reads for every write, and a redirect that has to be fast enough that nobody notices it happened.
- Chat Application System DesignLong-lived connections change everything: the hard part is not storing messages, it is knowing which of your servers is holding the socket you need to write to.
- Payment System DesignThe only system on this list where being approximately right is indistinguishable from being wrong, and where the provider, not you, holds the truth.
- RAG Pipeline ArchitectureAlmost every RAG system that disappoints is failing at retrieval, not generation, and the architecture is what decides whether you can tell.