Interview Setup
Interview Prompt
Design an OAuth 2.0 / SSO authentication system for 1B users that issues tokens, supports single sign-on across apps, and validates 500K tokens per second at the API gateway.
Clarifying Questions (ask before designing)
| Question | Why it matters |
|---|---|
| Public clients (mobile/SPA) or confidential server apps only? |
|
| Stateless JWT validation at gateway or server-side session lookup? |
|
| Token revocation required before expiry? |
|
| SSO across multiple third-party apps (OIDC) or internal only? | OIDC adds consent screen, redirect URI validation, and per-client scopes. |
Scope
In scope
- OAuth 2.0 flows (authorization code, PKCE)
- JWT lifecycle
- Refresh token rotation
- RBAC vs ABAC
- Session management
- Token revocation
Out of scope (state explicitly)
- Full HR employee directory / SCIM provisioning product
- Hardware security module manufacturing
- Building a social network on top
Functional Requirements
Start by asking your interviewer whether you are building an identity provider or integrating one. Confirm OAuth 2.0 flows, enterprise SAML SSO, and whether MFA is in scope for this round.
- User registration/login: Email+password, social login (Google, GitHub, Apple)
- OAuth 2.0 provider: Issue access/refresh tokens; support authorization code, PKCE, client credentials flows
- Single Sign-On (SSO): Login once, access multiple applications (SAML 2.0 and OIDC)
- Multi-Factor Authentication (MFA): TOTP, SMS OTP, WebAuthn/passkeys
- Role-Based Access Control (RBAC): Users have roles; roles have permissions
- API key management: Issue, rotate, revoke API keys for machine-to-machine auth
- Session management: Active session list, revoke sessions, device tracking
- Password policies: Minimum strength, breach detection, forced rotation
Non-Functional Requirements
Your interviewer will care most about token validation latency and five-nines availability. Auth down means the entire platform is down, so call out stateless JWT validation under 5 ms early.
- Low Latency: Token validation < 5 ms (stateless JWT); login < 500 ms
- High Availability: 99.999%: auth down = entire platform down
- Security: Bcrypt/Argon2 password hashing; token encryption; rate limiting on login
- Scale: 1B+ users, 500K+ auth requests/sec
- Compliance: SOC 2, GDPR (right to delete), PCI DSS for payment-adjacent
Capacity Estimations
Run this math before you size session storage. Auth requests per second and token validation ratio tell you Redis session load; user count drives credential store sharding.
| Metric | Calculation | Value |
|---|---|---|
| Total users | Given | 1B |
| Login requests / sec | Derived from daily volume ÷ 86400 (+ peak factor) | 50K |
| Token validation / sec | Derived from daily volume ÷ 86400 (+ peak factor) | 500K |
| Token refresh / sec | Derived from daily volume ÷ 86400 (+ peak factor) | 10K |
| Active sessions | Given | 500M |
Architecture Diagram
In the room: authorization code with PKCE for SPAs rather than implicit flow, explaining how refresh-token rotation detects compromise via reuse.
Walk your interviewer through the federation hub first. Auth is a federation hub: customer apps use OIDC/OAuth 2.0 with PKCE; enterprise customers connect via SAML 2.0. The identity provider issues short-lived access tokens and refresh tokens, stores sessions in Redis, and propagates logout across relying parties via back-channel or front-channel flows; I draw B2C OIDC and enterprise SAML as separate entry lanes. For broader caching and session storage patterns, see Caching Patterns and Invalidation, and for fraud mitigation at login, refer to Fraud Detection System.
Component Deep Dives
SAML 2.0 vs OIDC: When to Use Each
Next we walk through each component on the diagram. Evaluating SAML vs OIDC is the primary architectural fork when designing modern single sign-on systems.
OIDC is the modern default for SaaS and mobile; SAML persists for enterprise IdP integrations; state which you would pick for this use case.
OIDC (OpenID Connect) (Recommended) - Modern default:
- JSON/JWT tokens, REST-friendly, mobile-native
- Built on OAuth 2.0 authorization code + PKCE
- Use for: SaaS apps, mobile, SPAs, microservices
- Flow: /authorize → code → /token → id_token + access_token
SAML 2.0 - Enterprise legacy:
- XML assertions, browser POST binding, heavier
- Use for: enterprise SSO (Okta/ADFS → legacy apps)
- Flow: SP-initiated → IdP login → SAML assertion POST to /acs
Hybrid architecture:
- OIDC for customer-facing apps and API access
- SAML bridge for enterprise customers requiring federation
- Same user store; map SAML NameID → internal user_id
- Staff probe: "How do you handle SAML clock skew?" → NotBefore/NotOnOrAfter
with 5-min tolerance; NTP sync on IdP and SPJWKS Rotation and Key Compromise
JWKS rotation and key compromise response is a staff-level probe: explain the kid header, overlap window, and token_version bump on incident.
Signing keys (RS256): - Publish public keys at /.well-known/jwks.json with kid header - Rotate monthly: add new key, sign new tokens with new kid - Old keys remain in JWKS for verification-only (30-day overlap) Gateway caches JWKS for 1 hour. On unknown kid: 1. Force refresh JWKS (don't wait for TTL) 2. Retry validation once 3. If still unknown → 401 (possible key compromise or misconfig) Key compromise incident: 1. Revoke compromised kid immediately from JWKS 2. Bump token_version for all users (invalidates all access tokens) 3. Force re-login; rotate refresh tokens 4. Audit log: which tokens signed with compromised kid
OAuth 2.0 Authorization Code Flow with PKCE
PKCE is mandatory for public clients: explain why code_verifier prevents authorization code interception before walking through the six-step flow.
1. Client generates code_verifier + code_challenge = SHA256(code_verifier) 2. Client redirects to auth server: GET /authorize?response_type=code&code_challenge=... 3. User authenticates (login + MFA if enabled) 4. Auth server redirects back with authorization code 5. Client exchanges code for tokens at POST /token 6. Auth server validates code + verifier, returns tokens Why PKCE? Prevents authorization code interception attack. Without PKCE: intercepted code can be exchanged for tokens. With PKCE: code exchange requires code_verifier that only original client has.
JWT Token Structure and Validation
Access token (JWT):
Header: { "alg": "RS256", "kid": "key-2026-03" }
Payload: {
"sub": "user-uuid", "iss": "https://auth.example.com",
"aud": "api.example.com", "exp": 1710403200,
"scope": "read write", "roles": ["admin"],
"tenant_id": "tenant-uuid"
}
Validation at API Gateway (stateless, < 1ms):
1. Decode JWT -> get kid, look up public key from JWKS
2. Verify signature, check exp, iss, aud
3. Extract roles/permissions
Token revocation:
a. Short expiry (15 min) + refresh tokens
b. Token blacklist in Redis
c. Version counter in JWT + DBRBAC: Role-Based Access Control
Hierarchy: User -> has -> Roles -> have -> Permissions Example: User "Alice" -> roles: ["editor", "viewer"] Role "editor" -> permissions: ["article:create", "article:edit"] Role "viewer" -> permissions: ["article:read"] Permission format: resource:action Storage: user_roles + role_permissions in PostgreSQL Cache: in JWT + Redis
API Design
Authentication and Token Lifecycle APIs
REST endpoints for user registration, multi-factor login, token refresh rotation, and active session management.
POST /api/v1/auth/register
{ "email": "alice@example.com", "password": "SecureP@ss123", "name": "Alice" }
-> 201 { "user_id": "...", "email_verification_sent": true }
POST /api/v1/auth/login
{ "email": "alice@example.com", "password": "SecureP@ss123" }
-> 200 { "access_token": "eyJ...", "refresh_token": "tGz...", "expires_in": 900 }
OR 200 { "mfa_required": true, "mfa_token": "...", "mfa_methods": ["totp","sms"] }
POST /api/v1/auth/mfa/verify
{ "mfa_token": "...", "code": "123456", "method": "totp" }
-> 200 { "access_token": "eyJ...", "refresh_token": "tGz..." }
POST /api/v1/auth/token/refresh
{ "refresh_token": "tGz..." }
-> 200 { "access_token": "eyJ...(new)", "refresh_token": "tGz...(rotated)" }
POST /api/v1/auth/logout
-> 200 { "logged_out": true }
GET /api/v1/auth/sessions
-> { "sessions": [{ "device": "iPhone", "ip": "...", "current": true }] }
--- Common Errors ---
401 Unauthorized: invalid or expired token, wrong audience
403 Forbidden: insufficient scope or suspended account
429 Too Many Requests: login brute-force (over 5 attempts in 15 min)
503 Service Unavailable: auth DB failover in progress (retry with backoff)Common Error Responses
400 Bad Request: invalid input, missing required fields, or malformed JSON payload 401 Unauthorized: missing or invalid authentication token or API key 403 Forbidden: authenticated caller lacks required permissions for this resource 404 Not Found: requested resource ID does not exist 409 Conflict: duplicate write or version conflict, retry with a unique idempotency key 422 Unprocessable Entity: syntactically valid request failed semantic business validation 429 Too Many Requests: rate limit quota exceeded, client should honor Retry-After header 500 Internal Error: unexpected server failure, retry safely with an idempotency key 503 Service Unavailable: downstream dependency is unavailable or overloaded, retry with exponential backoff 422 Unprocessable Entity: password does not meet security requirements or MFA verification is required 423 Locked: account temporarily locked after repeated failed login attempts
Data Model
PostgreSQL
CREATE TABLE users (
user_id UUID PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255), name VARCHAR(100),
mfa_enabled BOOLEAN DEFAULT FALSE, mfa_secret VARCHAR(64),
token_version INT DEFAULT 0,
status ENUM('active','suspended','deleted'), created_at TIMESTAMPTZ
);
CREATE TABLE refresh_tokens (
token_id UUID PRIMARY KEY, user_id UUID NOT NULL,
token_hash VARCHAR(64) NOT NULL,
expires_at TIMESTAMPTZ NOT NULL, revoked BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE user_roles ( user_id UUID, role_name VARCHAR(50), PRIMARY KEY (user_id, role_name) );
CREATE TABLE role_permissions ( role_name VARCHAR(50), permission VARCHAR(100), PRIMARY KEY (role_name, permission) );Redis
session:{session_id} -> Hash { user_id, device, ip, last_active }
revoked_token:{jti} -> "", TTL = token remaining lifetime
permissions:{user_id} -> SET of permission strings, TTL 300
login_attempts:{email} -> INT (max 5 per 15 min), TTL 900Event Bus Design (Kafka)
Topic: auth-events
Partitions: 64
Partition key: user_id
Retention: 90 days (audit + threat detection)
Producer: Auth Service on login, logout, token refresh, MFA, password reset
Event: { event_id, user_id, event_type, ip, geo, device_fingerprint, success, timestamp }
Consumer groups:
1. audit-log: ClickHouse login history, geo analysis, anomaly detection
2. threat-detection: credential stuffing, impossible travel, brute force
3. revocation-broadcast: token revoke events to all regional Redis clusters
Sync path: JWT validation at API Gateway < 1ms (stateless JWKS cache)
Async path: audit and threat detection never block login response
DLQ: auth-events-dlqFault Tolerance
| Concern | Solution |
|---|---|
| Auth service down |
|
| Redis down | Token validation falls back to JWT-only (no revocation check) |
| Brute force |
|
| Credential stuffing |
|
| Token theft |
|
| Key compromise |
|
Additional Considerations
Interview Walkthrough
- 25-minute cut
Skip arch50/arch75 depth unless staff.
- Authorization code + PKCE for public clients (8 min)
- JWT validated at gateway (500K/sec) (9 min)
- Login 50K/sec hits auth service (8 min)
- Start with OAuth 2.0 authorization code flow with PKCE for SPAs instead of implicit flow, and never store tokens in localStorage without rotation.
- Walk through SSO: central IdP issues JWT access tokens (15 min) + opaque refresh tokens (7 days) with rotation on every refresh.
- Explain how refresh token rotation detects reuse, where an invalidated refresh token used again triggers revocation of all sessions as a compromise signal.
- Cover RBAC with permissions cached in Redis (5-min TTL) and invalidated on role change via pub/sub.
- Mention credential stuffing defenses: rate limits, CAPTCHA after 3 failures, breached-password checks, step-up auth for sensitive actions.
- Discuss token revocation via Redis blocklist keyed by JWT
jtiwith TTL matching remaining token lifetime. - Common pitfall: long-lived JWTs without refresh rotation, because a stolen access token grants hours of unauthorized access with no revocation path.
Engineering Trade-offs
Password Storage: Why Argon2id
Auth systems balance token convenience against revocation, weighing short-lived JWTs with refresh rotation against opaque server-side sessions.
bcrypt: good, but vulnerable to GPU attacks (fixed memory usage). scrypt: better (memory-hard), but complex to tune. Argon2id: winner of Password Hashing Competition. Memory-hard + CPU-hard. Configurable: time, memory, parallelism. Recommended: Argon2id with 64MB memory, 3 iterations, 4 threads. Each hash takes ~200ms and 64MB RAM -> GPU attacks infeasible.
Refresh Token Rotation
On every token refresh: 1. Validate current refresh_token 2. Issue new access_token + NEW refresh_token 3. Invalidate old refresh_token If stolen old refresh_token is used: It's already invalidated -> request fails Detection: invalidated token reuse -> compromise detected -> revoke ALL tokens
Credential Stuffing Defense
Defense layers: 1. Rate limiting: max 5 failed attempts per email per 15 min 2. IP rate limiting: max 50 attempts per IP per hour 3. CAPTCHA after 3 failed attempts 4. Device fingerprinting: new device + correct password -> email verification 5. Breached password check against HaveIBeenPwned API 6. Anomaly detection: new country, new device, unusual hour
Account Takeover (ATO) Detection
Signals of compromise: - Password changed + email changed within 5 minutes - Login from new country + immediate sensitive action - Session from unrecognized device + bulk data export Action: 1. Send notification to all active sessions 2. Require re-authentication for sensitive actions 3. If password AND email changed: lock account + send recovery to ORIGINAL email Step-up authentication: Sensitive actions require re-entering password even if session is valid.
Review
How helpful was this walkthrough?
Click a star to rate. We actively use this feedback to refine and update our system design content.
Discussion
Share your thoughts, ask questions, or help others.