System Design: Build an Authentication Service
A full whiteboard walkthrough — requirements, data model, flows, scale and failure — for the most common senior design prompt in this area.
"Design authentication for a platform with ten million users, a web app, a mobile app, and third-party integrations." Here is a structure that covers it in 45 minutes without rambling.
1. Clarify (3 minutes, and do not skip it)
- Which clients? Web, mobile, server-to-server, third parties — each pushes toward a different scheme.
- First-party only, or do external developers integrate? The second answer means OAuth.
- Is the web app same-site with the API?
- What revocation delay is acceptable? This single number decides most of the architecture.
- MFA? Enterprise SSO? Compliance regime?
- Read/write volume, and regions.
2. State the architecture in one sentence
"An OIDC-compliant authorization server issuing 10-minute RS256 access tokens and rotating opaque refresh tokens, with services verifying locally against a cached JWKS and a tokenVersion check for fast revocation."
3. Data model
users id, email, password_hash, token_version, mfa_enabled
identities user_id, provider, provider_sub (social / SSO)
memberships user_id, tenant_id, role (multi-tenant)
refresh_tokens hash, user_id, family_id, used_at, family_expires_at
auth_codes code_hash, client_id, pkce_challenge, expires_at, used_at
clients id, secret_hash, redirect_uris[], allowed_scopes[]
signing_keys kid, public_jwk, private_ref, active_from, retired_at
audit_log event, user_id, actor, ip, request_id, at4. Walk the flows
Web login, mobile login (code + PKCE in the system browser), third-party integration (OAuth with consent), machine-to-machine (client credentials or workload identity), refresh with rotation and reuse detection, and logout at three levels: this session, this device, everywhere.
5. Scale
Stateless verification keeps the hot path free of network calls. JWKS cached for ten minutes; tokenVersion cached five seconds; refresh tokens in Postgres because writes are rare. Token lifetimes jittered so a deploy does not create a synchronised refresh wave.
6. Failure
Auth server down: existing sessions work from cached keys, new logins fail — and you must say that failing open is never acceptable. Redis down: 503, not 401, so you do not log out the entire user base at once. Key compromise: rotate, which invalidates everything immediately.
7. Volunteer what you would not build
"I would use an existing OIDC provider unless there is a specific reason not to." Knowing when not to build an auth server is itself a senior signal.
Example
# The architecture, on one whiteboard
Web (abc.com) Mobile Partner app Internal services
│ │ │ │
│ code+PKCE │ code+PKCE │ code+PKCE │ client credentials
▼ ▼ ▼ ▼
┌──────────────────────────────────────────────────────────┐
│ Authorization Server (OIDC) │
│ /authorize /token /introspect /revoke /jwks /userinfo │
│ RS256 · access 10m · refresh 30d rotating │
└──────────────────────────────────────────────────────────┘
│ access token (JWT) ▲
▼ │ JWKS, cached 10 min
┌──────────────┐ internal token ┌────────────────────┐
│ API Gateway │──────────────────▶ │ Services │
│ verify+route │ │ verify + ownership │
└──────────────┘ └────────────────────┘
│
Postgres (RLS by tenant)
# Numbers to say out loud:
# access token 10 min → worst-case revocation delay
# tokenVersion cache 5s → revocation delay if we pay one cached lookup
# refresh 30 days → absolute session cap, rotation on every use
# JWKS cache 10 min → survives an IdP outage for existing tokensWhen to use it
- A candidate opens by asking for the acceptable revocation delay and uses the answer to justify every later decision.
- An interviewer probes the multi-region case and the candidate explains why stateless verification avoids a cross-region session lookup.
- A design round ends strongly because the candidate volunteered that they would buy rather than build unless a specific requirement forced it.
More examples
The endpoints, and what each one must enforce
Listing endpoints is easy and scores nothing. The annotations are what demonstrate you have thought about each one under attack.
// Sketch the surface, then annotate. The annotations are the interview.
POST /oauth/authorize // + PKCE challenge, exact redirect_uri match,
// state echoed back, consent for third parties
POST /oauth/token // grant: authorization_code | refresh_token |
// client_credentials | token-exchange
// code single-use; replay revokes issued tokens
POST /oauth/introspect // for opaque tokens; client-authenticated
POST /oauth/revoke // revoke a refresh family
GET /.well-known/jwks.json // two keys live during rotation
GET /.well-known/openid-configuration
GET /userinfo // requires the access token, not the ID token
POST /auth/register // throttled; generic errors; breach check
POST /auth/login // throttled per IP AND per account; backoff
POST /auth/mfa/verify // partial session → full; rotate the session id
POST /auth/logout // this session
POST /auth/logout-all // every family for the user + tokenVersion++
POST /auth/forgot // identical response whether or not the email exists
POST /auth/reset // single-use token; revokes ALL sessions on success
GET /auth/sessions // "active devices" — needs the per-user index
DELETE /auth/sessions/:id // revoke one device
// Cross-cutting, mentioned once rather than repeated per endpoint:
// every mutating endpoint → rate limited, audit logged, request-id traced
// every token issued → jti, iss, aud, exp, jittered lifetime
// every error → uniform shape, no existence disclosureThe revocation conversation, in full
Presenting options with numbers and then committing to one is the shape of a senior answer. Listing options without choosing reads as indecision.
INTERVIEWER: "A user is compromised. How fast can you lock them out?"
CANDIDATE: "Depends what we are willing to pay per request. Four options:"
1. Do nothing special — wait for expiry
delay: up to 10 minutes cost: zero
when : most products, honestly
2. tokenVersion on the user, checked against a 5-second cache
delay: ~5 seconds cost: one cached lookup per request
when : the usual sweet spot
3. jti denylist in Redis, TTL = remaining token life
delay: immediate cost: a Redis GET per request
when : per-token precision needed; list is self-pruning
4. Opaque tokens + introspection
delay: immediate cost: a network hop (cacheable ~5s)
when : regulated environments that mandate it
"I'd default to 2. It gets us within five seconds for one lookup we can cache
aggressively, and it is one integer on the user row."
INTERVIEWER: "And the UI says 'sign out everywhere' — is that honest?"
CANDIDATE: "With option 1, no — access tokens still work for up to ten minutes,
so either the copy should say 'within a few minutes' or we implement option 2.
I'd rather change the architecture than the copy, but it should be a decision
someone made, not an accident."
# Naming the dishonest-UI problem unprompted is a strong signal: it shows you
# think about the promise the product makes, not only the mechanism.Questions the interviewer has queued up
The last answer is worth rehearsing. Enthusiasm for building an auth server from scratch is usually read as inexperience with operating one.
Q: "Ten million users. Where is the bottleneck?"
A: Not verification — RS256 is ~50µs and needs no network. It is the refresh
endpoint (a database write per rotation) and login (Argon2 is deliberately
~250ms of CPU). Scale login horizontally and watch refresh write volume.
Jitter token lifetimes so a deploy does not synchronise the whole fleet.
Q: "Multi-region?"
A: Stateless verification is what makes it work — no cross-region session
lookup. Refresh tokens need a write, so either a regional primary with
sticky routing, or accept cross-region latency on refresh only. Signing
keys are replicated read-only; only one region mints.
Q: "A signing key leaks."
A: Rotate. Publish the new key, switch signing, remove the old from JWKS —
which invalidates every outstanding token immediately. Disruptive and
correct. This is why two keys must always be publishable.
Q: "How do you test this?"
A: Unit tests for verification with wrong aud, wrong iss, expired, alg=none.
Integration tests for the full flows. And five assertions in CI: another
user's record returns 404, a revoked credential returns 401, a foreign
origin gets no CORS headers, a tampered token returns 401, and 25 wrong
passwords produce a 429.
Q: "What would you NOT build?"
A: The authorization server itself, unless there is a reason. Auth0, Okta,
Keycloak or Ory are all correct answers, and the flows and failure modes
we just discussed are identical either way. I'd build the parts that are
specific to us — tenancy, roles, ownership — and buy the protocol.
Discussion