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
Should you build authentication or buy it?
Buy, unless authentication is your product. The surface here is large, the failure modes are severe, and the parts that look simple, password reset and account recovery, are where most real compromises happen. This diagram is more useful for understanding what a provider is doing on your behalf than as a build plan.
Sessions or JWTs?
Server-side sessions are simpler and revoke instantly, at the cost of a lookup per request. JWTs remove the lookup and make revocation hard. For a single application, sessions are usually the better and less fashionable answer.
Where should tokens be stored in a browser?
An httpOnly, Secure, SameSite cookie. Local storage is readable by any script on the page, which turns any cross-site scripting bug into full account takeover.
How should passwords be hashed?
Argon2id, or bcrypt where Argon2 is unavailable, with parameters tuned so hashing takes a noticeable fraction of a second on your hardware. The cost is the point: it is what makes offline cracking expensive if the database is ever taken.
More templates
- Event-Driven Microservices DesignDistributed transactions do not exist here, so every consistency guarantee you want has to be rebuilt out of events, retries and compensation.
- LLM Inference Service DesignGPUs are the budget, so almost every decision here is about keeping them busy without letting the queue destroy tail latency.
- CI/CD Pipeline ArchitectureTwo things decide whether people trust a pipeline: how fast it is, and whether a red build actually means something is broken.
- Ride Sharing System DesignEvery driver streams their position every few seconds whether or not anyone is riding, which makes this one of the few consumer systems where writes dominate reads.
Last updated