I11 · Part 2 · Intermediate
Security basics for design
Who is calling, what may they do, and what happens on the day a secret leaks.
09On this page
- Start with the threat, not the checklist
- Authentication is who you are; authorization is what you may do
- Sessions, tokens and OAuth: what each one is for
- Passwords, passkeys and second factors
- Authorization models: roles, attributes and relationships
- Encryption: in transit, at rest, and who holds the keys
- The attacks an interviewer expects you to name
- Dry run: reviewing a small design for flaws
- Recognizing it in an unseen problem
Start with the threat, not the checklist
Security in a design interview is not a list of acronyms. It is three questions asked in order: what are we protecting (accounts, payment data, other people's private messages), who might attack it (an anonymous visitor, a logged-in user going after someone else's data, an employee, a compromised dependency) and what do they gain if they succeed. Once you can say "the worst case here is one user reading another user's orders", every control you add has a reason, and every control you leave out is a decision you can defend.
Two habits carry most of the weight. Defense in depth: assume any single control will fail, so a stolen token should still be short-lived, still be scoped, still be logged. Least privilege: every user, service and credential gets only what its job needs, so a leak of one does not open everything.
Authentication is who you are; authorization is what you may do
Authentication establishes identity (a password, a passkey, a token). Authorization decides whether
that identity may do this thing to this object. The two are separate checks, and the second is the one that fails
in real systems: broken access control is the first category of the OWASP Top 10 in both the 2021 and the 2025 editions. The
classic form is the insecure direct object reference. A request for GET /orders/8731 is
authenticated, so the server returns the order, never checking that order 8731 belongs to the caller. Changing the
number in the URL then walks through every customer's orders.
⚠ Check ownership on the server, on every request, deny by default
Hiding a button or an id in the client protects nothing. The rule that holds up is: every handler loads the
object, checks that the caller may touch this object with this action, and refuses when no rule
says yes. A check that exists only in the gateway is not enough, because a second route or an internal caller
skips it.
Sessions, tokens and OAuth: what each one is for
| Mechanism | What it is | Strength | Weakness |
|---|---|---|---|
| Session cookie | A random id pointing at server-side state | You can revoke it instantly by deleting the row | Needs a shared session store once you have several servers |
| Opaque access token | A random string the API checks with the auth server | Revocable, and carries no claims (though a leaked one is still a usable bearer token) | An extra call per request, unless you cache the answer |
| JWT access token | A signed, self-contained token: identity and claims inside | Any service verifies it locally with a public key, no lookup | Cannot be revoked before it expires, so keep it short-lived (minutes) |
| Refresh token | A longer-lived credential that only mints new access tokens | Lets the access token stay short | A stolen one is powerful: rotate it on every use and store it carefully |
OAuth 2.0 is a framework for delegated authorization: a user lets one application act on their behalf at another without giving it their password. It is not a login protocol by itself. OpenID Connect adds an ID token on top, which is the part that says who the user is. For a browser or mobile app the flow is the authorization code flow with PKCE: the app sends the user to the identity provider with a hashed one-time challenge, the provider returns a short-lived code, and the app exchanges the code plus the original secret for tokens. The current OAuth security best-practice document (RFC 9700) says public clients must use PKCE, that the password grant must not be used and the implicit grant should not be, and that redirect URIs must match exactly.
JWT pitfalls are worth naming, because the format looks safer than it is. RFC 8725 lists them. Verify the
signature with the algorithm you expect, never the one the token's own header claims, and reject
none. Check the issuer, the audience and the expiry on every use. The payload is only base64url
encoded, so anyone can read it: never put a secret in it. Plan key rotation with a key id so that you can
publish a new key before retiring the old one. And on the client, a cookie marked HttpOnly keeps the token away from scripts (Secure
and SameSite limit where it is sent), while a token in localStorage is readable by any
cross-site scripting bug. A script can still make requests from the page while it is open, and cookies bring
request forgery back, so neither choice replaces fixing the script bug.
Passwords, passkeys and second factors
Never store a password, and never store anything that can be turned back into one. Store the output of a slow, memory-hard hash with a unique random salt per user. The OWASP password storage cheat sheet puts Argon2id first, with minimum settings of about 19 MiB of memory, 2 iterations and 1 degree of parallelism, then scrypt, and keeps bcrypt (work factor of at least 10, and a 72-byte input limit) for legacy systems; PBKDF2 at a very high iteration count is the option where FIPS validation is required. A pepper, a secret kept outside the database, adds defense in depth, but it is not a substitute for the salt and the slow hash. Compare hashes in constant time and rate limit the login endpoint (see the rate limiting chapter), keyed by address as well as by account.
The stronger move is to need fewer passwords. A passkey (WebAuthn) is a public-key credential held by the user's device: the server stores only a public key, so there is nothing to phish and nothing to leak from your database that an attacker can replay on another site. A second factor (a one-time code, a hardware key) limits the damage when a password is stolen anyway.
Authorization models: roles, attributes and relationships
| Model | Rule looks like | Good for | Strain |
|---|---|---|---|
| RBAC (roles) | "Admins can delete projects" | Internal tools, a small fixed set of job types | Role explosion once each team and project needs its own variation |
| ABAC (attributes and policies) | "Editors can edit documents in their own department during business hours" | Rules that depend on context | Policies get hard to audit and test |
| ReBAC (relationships) | "A user can view a document if they are a viewer of its folder, or a member of a group that is" | Sharing graphs: documents, folders, teams | Needs a store built for graph checks and a plan for consistency |
Google's Zanzibar paper describes the relationship model at scale: access is stored as tuples of the form object, relation, user, and a check walks them, which is how one system answers "may Alice open this file?" for Drive, Photos and YouTube. Where it gets hard is exactly the part earlier chapters warned about: a permission cache is a replica, so a revoked permission keeps working for as long as a stale answer lives. Decide how stale an authorization answer may be, and let the sensitive actions bypass the cache.
Encryption: in transit, at rest, and who holds the keys
- In transit: TLS everywhere, including between your own services, because a flat internal network is not a security boundary. Mutual TLS makes both ends present certificates, which gives each service a verifiable identity (a service mesh automates the certificates).
- At rest: disk or volume encryption protects against a stolen disk and a discarded server. It does nothing against an attacker who gets your application's credentials, because the application sees decrypted data. For sensitive fields (card numbers, national ids) encrypt or tokenize them in the application so the database never holds plaintext.
- Envelope encryption: a key management service holds a root key (AWS calls it a KMS key) that never leaves it. For each object you generate a data key, encrypt the data with it, then encrypt the data key with the master key and store the two together. Changing the wrapping key means re-wrapping small data keys, not re-encrypting terabytes (AWS KMS's automatic rotation keeps the old key material, so even that is needed only when you move to a new key).
- Secrets: keep them in a secrets manager, inject them at runtime, give them a short life or rotate them, and never commit them to a repository, bake them into an image or write them to a log. A leaked secret should mean "rotate and redeploy", not "emergency".
The attacks an interviewer expects you to name
| Attack | What happens | Design-level defence |
|---|---|---|
| Injection (SQL, command) | Input is interpreted as code | Parameterized queries, never string-built SQL; validate on the server |
| Cross-site scripting | An attacker's script runs in your page and reads tokens or acts as the user | Encode output by context, a restrictive Content Security Policy, HttpOnly cookies |
| Cross-site request forgery | Another site makes the user's browser send an authenticated request | SameSite cookies, anti-forgery tokens on state-changing requests |
| Server-side request forgery | You fetch a URL the attacker chose, and reach an internal service or the cloud metadata address | Allowlist outbound destinations, resolve and then pin the address (against DNS rebinding), disable redirects, restrict egress at the network layer, and on AWS require the token-based IMDSv2 metadata service |
| Credential stuffing | Leaked passwords from other sites are tried against yours | Rate limits by address and device, breached-password checks, passkeys and a second factor |
| Denial of service | Volume or expensive requests exhaust capacity | CDN and edge filtering, per-client limits and load shedding (see those chapters) |
| Supply chain | A dependency or build step is compromised | Pin and verify dependencies, scan them, build from a clean, reviewed pipeline |
Dry run: reviewing a small design for flaws
A notes app stores each user's notes. Its API is GET /notes/{id}. Login returns a JWT valid for 24
hours, which the single-page app keeps in localStorage. Passwords are stored as an unsalted SHA-256
of the password. A design review finds four problems.
| Flaw | What an attacker does | Fix |
|---|---|---|
| The handler returns any note by id | Loops over ids and reads everyone's notes | Check note.owner == caller in the handler; use random ids as a second layer, not the first |
24-hour token in localStorage | One cross-site scripting bug steals a token that works for a day | Cookie with HttpOnly, Secure and SameSite; a 10 to 15 minute access token plus a rotating refresh token |
| Unsalted fast hash | After a database leak, the same hash means the same password, and guessing is cheap | Argon2id with a per-user salt |
| No limit on login attempts | Credential stuffing runs with nothing slowing it down | Per-address and per-account limits and a breached-password check |
How cheap is the guessing? An eight-letter lowercase password has 268 = 208,827,064,576 possibilities. At an illustrative 10 billion fast hashes a second on a single modern graphics card, that is about 21 seconds to try every one. A memory-hard hash with a salt does not make a bad password good, but it turns a 21-second job into one that costs real money per guess and cannot reuse work across users.
Say it like this → "First the worst case: one user reading another's data, so every
handler checks ownership, not just the gateway. Login is the authorization code flow with PKCE through a managed
identity provider, with a 15-minute access token in an HttpOnly cookie and a rotating refresh token. Passwords, if
we hold any, are Argon2id with a per-user salt, and I'd offer passkeys. Services talk over mutual TLS, secrets come
from a secrets manager and rotate, and sensitive fields are encrypted in the application with envelope keys. I'd
rate limit login by address and account, and log every authorization failure."
Recognizing it in an unseen problem
- Signals: any mention of users, accounts, payments, health data, multi-tenant data, or a third-party login. Say the security section out loud even if the prompt does not ask: it is expected at senior level.
- The naive design authenticates at the edge and trusts everything behind it. Name the missing per-object authorization check, the long-lived token and the plaintext secrets.
- Multi-tenant prompts (one service, many customers) make the tenant id part of every key, query and cache entry. A missing tenant filter is the data-leak bug.
- Distinguish authentication, authorization and encryption: they fail in different ways and need different fixes.
- Quantify the blast radius: "a leaked access token is good for 15 minutes and one scope" is a design statement. "We use JWT" is not.
- The pitfall: inventing your own cryptography or token format. Use a managed identity provider, a standard flow and a vetted library.
Before you move on
- State the difference between authentication and authorization, and give the object-reference bug as an example of the second failing.
- Describe the authorization code flow with PKCE and say what attack the verifier prevents.
- Say why a JWT cannot be revoked early, and what you do about it.
- Say how you store a password and why a fast hash is the wrong tool.
- Explain envelope encryption and what changing the wrapping key costs.
- Name four attacks and the design-level defence for each.
Sources and further reading
- OWASP Top 10 (2025 edition) — the current list of web application risks, with broken access control first
- RFC 9700: OAuth 2.0 security best current practice — PKCE, exact redirect matching, no implicit or password grants
- RFC 7636: Proof Key for Code Exchange — the verifier and challenge mechanism
- RFC 6749: the OAuth 2.0 framework — roles, grants and tokens
- RFC 8725: JSON Web Token best current practices — algorithm checks, audience and issuer validation
- OWASP password storage cheat sheet — Argon2id minimums, bcrypt limits, pepper
- NIST SP 800-63B: authentication and lifecycle management — password and authenticator guidance
- W3C Web Authentication — passkeys and public-key credentials
- Zanzibar: Google's consistent global authorization system — relationship tuples and checks at scale
- AWS KMS: cryptography basics — data keys, master keys and envelope encryption
Claims in this chapter were checked against these sources in October 2026. The hash rate in the dry run is an illustrative figure.
5 questions drawn from 7, scored out of 5. This is practice: it runs in your browser, saves nothing and does not change what you have marked read.
Finished reading?
0 of 36 chapters read in this series