Design problems
Platform & infrastructureHardSDE-3 35 min

Design an authentication and SSO service

Asked at
AtlassianGoogleMicrosoftPhonePeSalesforceZoho

The interviewer asks

“Design the central authentication and single sign-on service for a company with several products (web, Android, iOS) and 100 million users. Users log in once and stay logged in across products; enterprise customers want to sign in with their own identity provider; and 300 internal services need to verify who is calling them.”

What a strong answer looks like

A strong answer builds an OpenID Connect provider (or wraps Keycloak/Auth0/Cognito) and is precise about the protocol: Authorization Code + PKCE for web and mobile, Client Credentials (or workload identity, mTLS) for service-to-service, federation with SAML/OIDC for enterprise SSO. It issues short-lived signed access tokens (JWT, 5-15 min) that APIs verify locally against a cached JWKS, plus long-lived opaque refresh tokens stored server-side with rotation and reuse detection, which gives revocation. SSO across products comes from a central session cookie on the auth domain. The deep dives: token design and revocation (stateless JWT vs session lookups), refresh token rotation, key rotation, login security (rate limits, OTP abuse, credential stuffing, MFA), and availability, since auth down means everything down. India-specific: phone + OTP is the main login, so OTP cost and SMS pumping fraud matter.

Concepts to mention

OIDC vs OAuth2: ID token vs access tokenAuthorization Code + PKCE for web and mobileshort-lived JWT access tokens verified locally via JWKSopaque refresh tokens with rotation and reuse detectionrevocation strategy and its delaysigning key rotation with kidSAML/OIDC federation and SCIM for enterprise SSOclient credentials or mTLS for service-to-serviceOTP and login rate limiting, credential stuffing defenceArgon2id/bcrypt password hashing

1 Requirements

Functional

  • Sign up and log in with phone + OTP, email + password, and Google/Apple social login
  • Single sign-on across all company web and mobile products
  • Enterprise SSO: customers connect their Okta/Azure AD via SAML 2.0 or OIDC; optional SCIM user provisioning
  • Multi-factor authentication (TOTP, passkeys/WebAuthn) and step-up auth for risky actions
  • Issue access tokens with scopes and claims; refresh tokens for long sessions
  • Logout everywhere, revoke sessions per device, admin force-logout for a compromised account
  • Service-to-service authentication for internal APIs
  • Login history and security alerts (new device, new location)

Non-functional

  • Availability 99.99% for token verification and refresh; login 99.95%
  • Token verification in APIs adds under 1 ms (local signature check, no network call)
  • Login p95 under 500 ms excluding OTP delivery; OTP delivered within 10 s for 95% of users
  • Revocation effective within 5-15 minutes for normal logout, within seconds for security incidents
  • Scale: 100M users, ~5M logins/day, ~20k token refreshes/s at peak
  • Passwords hashed with a slow memory-hard function; no secrets or tokens in logs

Out of scope (say it out loud)

  • Fine-grained authorization policies inside each product (only coarse scopes and roles here)
  • KYC and identity verification
  • Customer-facing account management UI design

2 Estimates

Logins~60/s average, ~600/s peak5M logins/day ÷ 86,400 ≈ 58/s; ×10 at morning peaks and sale launches
Token refreshes~10-20k/s peak~20M concurrently active sessions; access token TTL 15 min → 20M ÷ 900 s ≈ 22k/s at worst, lower with jitter
Token verifications~2M/s, all localEvery API call at every service verifies the JWT signature in-process (~20-50 µs for ES256/RS256 verify), so no auth-service load
OTP SMS cost~₹1-1.5 lakh/day~3M OTP logins/day × 1.2 sends (resends) × ~₹0.15-0.25 per SMS; why OTP abuse limits matter
Refresh token store~100M rows, ~30 GBUp to ~1 active session per user per device; ~300 B per row with metadata
Password hashing CPU~10 cores at peak for password loginsArgon2id tuned to ~50 ms and ~64 MB per hash; 200 password logins/s × 50 ms ≈ 10 CPU-seconds/s

3 API

EndpointWhat it does
GET/oauth2/authorize?response_type=code&client_id=&redirect_uri=&scope=openid%20profile&state=&code_challenge=&code_challenge_method=S256Starts the Authorization Code + PKCE flow; shows login if there is no SSO session cookie, else redirects back with a code.
POST/oauth2/tokenExchange code + code_verifier for tokens; refresh_token grant with rotation; client_credentials for services.
GET/.well-known/openid-configuration and /oauth2/jwksDiscovery document and public signing keys; cached by every service.
POST/v1/otp/send and /v1/otp/verifyPhone login: send OTP (rate limited per phone, IP, device) and verify (max 5 attempts).
POST/oauth2/revoke and /v1/sessions/{id}:revokeLogout one device or all devices; revokes refresh tokens and publishes a revocation event.
POST/saml/acs/{connection_id}SAML assertion consumer for enterprise IdPs; validates signature, audience, time window.
GET/oauth2/userinfoOIDC userinfo for clients that need profile claims.

4 Data model

usersPostgres (sharded by user_id, or Aurora/Spanner-class)
user_id (PK)phone_e164 (unique)email (unique)password_hash (argon2id)mfa_methodsstatuscreated_at

Unique indexes on phone and email; lookups by identifier on login. Data stays in Indian regions where required.

identitiesPostgres
user_idprovider (google|apple|saml:conn_id)subjectlinked_at; unique(provider, subject)

Links external identities to one user; account linking rules prevent takeover via unverified emails.

sessions / refresh_tokensPostgres + Redis cache
session_iduser_iddevice_infotoken_family_idcurrent_token_hashparent_hashexpires_atrevoked_atlast_used_ip

Store only a SHA-256 hash of the refresh token. Family id enables reuse detection: an old token used again revokes the whole family.

signing_keysKMS/HSM + Postgres metadata
kidalg (ES256)public_keystatus (next|active|retiring)created_at

Private keys never leave KMS/HSM or are held encrypted; JWKS publishes active + next + retiring keys.

otp_challengesRedis
key otp:{phone}value {code_hashattemptsexpires_at}TTL 5 min

Hash the code; cap verify attempts; separate counters for send rate per phone, IP and device.

sso_connectionsPostgres
tenant_idprotocol (saml|oidc)idp_metadatacertsemail_domainsenforce_ssoscim_token_hash

Email-domain routing: user types name@bigcorp.com and is sent to BigCorp's IdP.

revocation listRedis + pub/sub to services
revoked jti or user_id → revoked_after timestampTTL = max access token lifetime

Only for urgent revocations; small because entries expire with the access token TTL.

5 Architecture

CLIENTSEDGESERVICESASYNCDATAEXTERNALlogin / refreshBearer JWTJWKS (cached)users, sessionsOTP + rate limitssign tokenssend OTPenterprise loginSAML / OIDClogin, revokerevocation pub/subWeb apps (browser)Android / iOS appsWAF + API gatewayAuth / OIDC serviceProduct APIs (JWTverify)Federation (SAML/OIDC)Auth events (Kafka)Risk + alerts workerPostgresusers/sessionsRedis OTP, limits,revokeKMS / HSM signing keysSMS / WhatsApp OTPCustomer IdP (Okta,AAD)
ClientEdge / gatewayService / workerQueue / streamCacheDatabase / storageAI / modelThird partyrequestasync (event / queue)

Hover a box to highlight its connections.

6 Request flows

Mobile login with phone OTP and PKCE

  1. 1The app generates a random code_verifier and its SHA-256 code_challenge, then opens /oauth2/authorize in a system browser tab (not an embedded WebView).
  2. 2The user enters a phone number. The auth service checks send limits (e.g. 3 per phone per 10 min, 20 per IP per hour, device fingerprint) and sends a 6-digit OTP via the SMS provider, with WhatsApp as a fallback.
  3. 3The OTP hash is stored in Redis with a 5-minute TTL and an attempts counter. The user enters the code; after 5 wrong attempts the challenge is burned.
  4. 4On success, the service creates an SSO session (HttpOnly, Secure, SameSite cookie on auth.example.com) and redirects to the app's redirect URI with a one-time authorization code bound to the code_challenge.
  5. 5The app POSTs the code and code_verifier to /oauth2/token. The server checks SHA-256(verifier) = challenge, so an intercepted code is useless.
  6. 6It returns an access token (ES256 JWT, 15 min, claims: sub, aud, scope, exp, jti, sid) and an opaque refresh token (stored hashed, with a token family id). A login event goes to Kafka for risk checks and the 'new device' alert.

API call and token refresh

  1. 1The app calls GET /orders with Authorization: Bearer <JWT>. The orders service verifies the signature with the cached JWKS key matching kid, then checks iss, aud, exp and scope. No network call.
  2. 2After 15 minutes the token expires; the app calls /oauth2/token with grant_type=refresh_token.
  3. 3The auth service hashes the presented token, finds it is the current token of family F, issues a new refresh token, marks the old one as used, and returns a new access token.
  4. 4If an already-used refresh token is presented again (stolen and replayed), the service revokes the entire family F and forces re-login on that device.

Enterprise SSO via SAML

  1. 1A user types priya@bigcorp.com; the email domain maps to BigCorp's SAML connection with enforce_sso = true.
  2. 2The federation service redirects to BigCorp's IdP with a signed AuthnRequest; the user logs in with corporate MFA.
  3. 3The IdP posts a signed assertion to /saml/acs; the service validates signature, audience, recipient, NotOnOrAfter and the InResponseTo id (replay protection).
  4. 4The user is mapped via identities(provider=saml:bigcorp, subject) and a normal session and tokens are issued; SCIM deprovisioning later revokes all their sessions.

7 Deep dives

Access tokens: JWT vs opaque, and how to revoke

A JWT access token is signed by the auth service; any API can verify it locally with the public key from the JWKS endpoint, so 2M verifications per second cost the auth service nothing. The weakness is revocation: a JWT is valid until exp, even after logout. An opaque token (random string) requires a lookup (introspection) on every call, which is easy to revoke but puts the auth store on every request path. The standard answer is a hybrid. Access tokens are short-lived JWTs (5-15 minutes) and contain only stable claims (sub, aud, scope, tenant, sid), not data that changes often. Refresh tokens are opaque, long-lived (30-90 days with sliding expiry), stored hashed in the database, and checked on every refresh, so logout revokes the refresh token and the access token dies within 15 minutes. For urgent cases (account takeover, employee offboarding) publish a small revocation entry (sid or user_id revoked_after T) via Redis pub/sub to services, which check it in memory; entries expire after 15 minutes because the tokens would have expired anyway. Always validate alg against an allow-list (never accept 'none' or switch RS256 to HS256), and check iss and aud so a token minted for one product cannot be replayed against another.

Refresh token rotation and reuse detection

Mobile apps keep users logged in for months, so the refresh token is the crown jewel. Store it in the Android Keystore / iOS Keychain, never in plain shared preferences; for browsers, keep tokens out of JavaScript where possible (a backend-for-frontend holds them and the browser gets an HttpOnly session cookie). On the server, rotate on every use: each refresh returns a new refresh token and marks the old one used, all linked by a token family id. If a used token comes back, either an attacker or the real client is replaying a copy, so the server revokes the whole family and the user logs in again. Handle the honest race: an app with two parallel requests may refresh twice within a second, or a network failure may drop the response. Allow a short grace window (e.g. 10-30 s) where the previous token returns the same new token, so legitimate retries do not log users out. Store only SHA-256 hashes of tokens so a database leak does not give out sessions, and record device, IP and last-used time so users can see and revoke sessions.

Signing keys and rotation

Tokens are signed with an asymmetric key (ES256 is smaller and faster than RS256; EdDSA is also used). The private key lives in KMS/HSM or is encrypted at rest, and only the auth service can use it. Each key has a kid that is placed in the JWT header. Rotation without downtime works in three phases: publish the new public key in JWKS as 'next' (services refresh JWKS every few minutes and cache it), wait longer than the JWKS cache time, then start signing with the new key, and keep the old public key in JWKS until all tokens it signed have expired (15 minutes plus margin). Services should refetch JWKS when they see an unknown kid (with a rate limit to stop attackers forcing refetches). If a key is compromised, remove it from JWKS immediately and accept that every session using it must refresh. Calling KMS for every signature adds latency and cost at 20k tokens/s, so a common pattern is a short-lived signing key generated in memory and certified by the KMS root, or KMS with local caching of data keys.

Login abuse: OTP pumping and credential stuffing

In India most consumer logins use phone + OTP, so the OTP endpoint is both the front door and a money leak. Attackers trigger millions of OTP sends to premium-rate or random numbers (SMS pumping), costing lakhs per day, or try to brute-force 6-digit codes. Defences: rate limits per phone, per IP, per /24 subnet, per device id and globally per country prefix; CAPTCHA or Play Integrity / App Attest checks before sending; exponential cool-down for resends; at most 5 verify attempts per challenge; and alerts when send volume deviates from normal by hour. Codes are random 6 digits, stored hashed, valid 5 minutes, single use. For passwords, credential stuffing (lists of leaked passwords) is the main threat: hash with Argon2id or bcrypt, check new passwords against breached-password lists, rate-limit per account and per IP with progressive delays, use risk scoring (new device, impossible travel) to require MFA, and return the same error message for 'unknown user' and 'wrong password'. Passkeys (WebAuthn) remove phishing risk entirely and are worth offering.

8 Trade-offs

Build your own OIDC provider vs Keycloak / Auth0 / Cognito

Security protocols are easy to get subtly wrong (redirect URI matching, PKCE, SAML signature wrapping attacks). Managed providers or Keycloak give certified flows quickly. Building your own makes sense at very large scale, for full control of user experience (OTP flows, regional languages), cost, and data localisation. Many companies run Keycloak or a managed provider for enterprise SSO and their own service for high-volume consumer OTP login.

Short vs long access token lifetime

Short (5 min) gives faster revocation but more refresh traffic and more sensitivity to auth outages. Long (1 hour) cuts load but leaves stolen tokens valid longer. 10-15 minutes plus a fast revocation channel for emergencies is a common balance.

Central session cookie SSO vs token sharing

SSO across products works by every product redirecting to the same auth domain, which already has a session cookie, so the user sees no login form. Sharing tokens between apps directly or using third-party cookies breaks with browser privacy changes. On mobile, a shared system browser session or an account manager achieves the same.

9 Bottlenecks

Refresh storm after an outage

If the auth service is down for 20 minutes, almost every access token expires, and on recovery 20M clients refresh at once. Clients should use jittered retries; the service should prioritise refreshes over new logins, and access tokens can carry a slightly randomised TTL so expiries spread out.

Session store hot path

At 20k refreshes/s, every refresh writes the session row. Shard sessions by user_id, keep writes minimal (one row update), and cache session lookups in Redis with write-through.

10 Failure handling

Auth service down

Existing access tokens keep working because verification is local, so most traffic is unaffected for up to the token lifetime. New logins and refreshes fail; apps should keep using cached tokens and retry with backoff. Run the auth service active-active across availability zones and regions, with the users database replicated, because auth is the most critical dependency.

SMS provider outage

OTP delivery fails. Use two or three SMS providers with health-based routing, a WhatsApp or voice-call fallback, and longer-lived sessions so fewer users need to log in during the outage.

Signing key leaked

Remove the key from JWKS, sign with a new key, and force all sessions to refresh. Because refresh tokens are opaque and server-side, users stay logged in after one refresh; only tokens signed with the leaked key stop working.

11 What impresses

  • States clearly that JWTs cannot be revoked before exp and designs the hybrid with numbers
  • Explains refresh token reuse detection and the grace window for honest races
  • Walks through zero-downtime key rotation with JWKS caching
  • Brings up OTP SMS pumping cost and concrete limits per phone, IP and device
  • Notes that auth outage affects everything and designs for local verification and multi-region
  • Mentions PKCE, system browser instead of WebView, and alg/aud validation pitfalls

12 Mistakes

  • Putting long-lived JWTs (days) on clients with no revocation plan
  • Every API calling the auth service to validate every token
  • Using the implicit flow or storing tokens in localStorage without considering XSS
  • Not checking aud and iss, or accepting the alg from the token header
  • Storing refresh tokens or OTPs in plain text
  • No rate limits on OTP send and verify

13 Follow-ups

Q1A user's phone is stolen. How do they log out of that device from the web?
Q2How do you migrate 100M users from bcrypt to Argon2id without forcing a password reset?
Q3How does a backend service call another on behalf of a user (token exchange)?
Q4How do you handle a phone number that is recycled by the telecom company to a new person?
Q5How would you support login for users with no internet-quality connection (OTP latency on 2G)?
Q6How do you give an AI agent limited access to a user's account?

Saved on this device. Create a free account to keep it on every device.

Practise this with Sam

A real mock interview, out loud, with an honest scorecard. Free to start.

Start free