Reliability

Security Fundamentals

System design security covers proving who a caller is (authentication), deciding what they may do (authorization), protecting data in transit and at rest, and defending against the common attack classes. The recurring themes are defense in depth and never trusting the network.

Authentication vs Authorization, Sessions vs JWTs

Authentication (authn) establishes who you are; authorization (authz) decides what you may do. They are separate layers with separate failure modes: a broken authn lets attackers in as someone else, while broken authz (like IDOR, changing /orders/123 to /orders/124 and reading someone else's data) lets legitimate users do illegitimate things. Broken access control sits at the top of the OWASP Top 10 for a reason: authz must be enforced server-side on every request against the resource's owner, never inferred from what the UI happens to show.

Server-side sessions are the classic web model: on login the server stores a session record and gives the browser an opaque random ID in a cookie (flagged HttpOnly, Secure, SameSite). The server holds all state, which means instant revocation (delete the session row and the user is out now) at the cost of a session-store lookup per request, typically Redis with roughly 1ms latency, and the session store becoming shared infrastructure across services.

JWTs invert this: the server signs a token containing the claims (subject, roles, expiry) and any service holding the public key can verify it statelessly, no lookup, which is why JWTs dominate service-to-service and microservice edge auth. The cost is revocation: a signed token is valid until it expires no matter what, so a stolen token or a fired employee's token keeps working. The standard mitigation is short-lived access tokens (5 to 15 minutes) paired with long-lived refresh tokens that are stateful and revocable, plus optionally a denylist checked for high-value operations, at which point you have partially reinvented sessions and should say so. Implementation hygiene interviewers listen for: verify the algorithm (reject alg none, do not accept HS256 where RS256 is expected), validate issuer, audience, and expiry, and keep tokens out of localStorage when XSS is a concern.

OAuth2, OpenID Connect, and API Keys

OAuth2 is a delegated authorization framework: it lets a user grant a third-party app limited access to their resources without sharing their password. The roles are resource owner (the user), client (the app), authorization server (issues tokens), and resource server (the API). The flow to know cold is authorization code with PKCE: the client redirects the user to the authorization server, the user authenticates and consents, the client receives a one-time code and exchanges it (with a code verifier proving it initiated the flow) for an access token. PKCE closed the code-interception hole and is now recommended for all clients, including web apps; the older implicit flow is deprecated. Client credentials flow covers machine-to-machine auth with no user involved.

A point that reliably distinguishes candidates: OAuth2 by itself is authorization, not authentication. Sign in with Google is OpenID Connect (OIDC), an identity layer on top of OAuth2 that adds a signed id_token (a JWT with standard identity claims) so the client learns who the user is, not merely that it can call an API. Using a bare OAuth2 access token as proof of identity is a known vulnerability pattern.

API keys are the simplest credential: a static random string identifying a calling application, suited to server-to-server integrations and usage tracking (billing, rate limiting per key). Their weaknesses are that they are long-lived bearer secrets with no user context, so they demand hashing at rest (treat them like passwords), scoped permissions, per-key rate limits, rotation support, and secret scanning, since leaking keys in public GitHub repos is one of the most common real-world breach vectors. A typical mature stack: OIDC for humans at the edge, OAuth2 client credentials or mTLS for service-to-service, API keys only for external developer APIs.

TLS and Encryption at Rest and in Transit

Encryption in transit means TLS everywhere. The TLS handshake uses asymmetric cryptography and certificates to authenticate the server (a CA-signed certificate chain proving the server owns the domain) and to agree on symmetric session keys that encrypt the actual traffic; TLS 1.3 cut the handshake to one round trip and removed known-weak ciphers. Modern deployments get certificates free and auto-rotated via Let's Encrypt and ACME. Two design decisions come up in interviews: terminate TLS at the load balancer (cheaper, centralized certificates, but plaintext behind it) versus end-to-end TLS between services, and whether internal service-to-service traffic uses mutual TLS (mTLS), where both sides present certificates, giving every service a cryptographic identity. Service meshes like Istio exist substantially to automate mTLS certificate issuance and rotation.

Encryption at rest protects stored data against stolen disks, snapshots, and improperly decommissioned hardware. The standard architecture is envelope encryption: data is encrypted with a data encryption key (DEK), the DEK is itself encrypted by a key encryption key (KEK) living in a KMS or HSM, and access to the KMS is audited and IAM-controlled. This makes key rotation cheap (re-encrypt the small DEKs, not terabytes of data) and enables crypto-shredding: destroy the key and the data is effectively erased, a practical answer for GDPR deletion in append-only stores. Know the layers: full-disk encryption (protects against physical theft only), database-level transparent encryption, and application-level field encryption for the most sensitive columns, which protects even against a compromised database but breaks indexing and querying on those fields.

The boundary to state clearly: encryption at rest does nothing against an attacker who compromises the running application, because the app can decrypt by design. That threat is addressed by access control, least privilege, and auditing, which is a defense-in-depth point interviewers reward.

Common Attacks and Zero Trust

Injection attacks smuggle attacker-controlled data into an interpreter. SQL injection (the canonical ' OR 1=1 --) is fully solved by parameterized queries, never string concatenation, with ORMs safe by default and input validation as a secondary layer only. XSS injects script into pages viewed by other users, mitigated by contextual output encoding (framework auto-escaping), Content-Security-Policy headers, and HttpOnly cookies so stolen-script access to tokens is limited. The pattern across all of them is the same: never mix code and data channels; keep untrusted input inert.

DDoS attacks exhaust resources. Volumetric floods (hundreds of gigabits to terabits per second from botnets; major clouds have absorbed multi-terabit attacks) are absorbed by CDN and edge providers with massive anycast capacity, which is why the practical answer is Cloudflare, AWS Shield, or equivalent, not something you build. Protocol attacks (SYN floods, mitigated by SYN cookies) and application-layer attacks (expensive endpoints like search or login hammered at low volume) are subtler; the latter are fought with rate limiting per IP/user/key, CAPTCHAs on abuse signals, caching, and making expensive endpoints cheaper. Defense in depth stacks these: edge scrubbing, then WAF, then rate limits, then per-service bulkheads.

Zero trust replaces the castle-and-moat model (hard perimeter, trusted internal network) with never trust, always verify: every request is authenticated and authorized regardless of network origin, because perimeters fail (VPN compromise, phishing, insider threat, lateral movement after any single host is popped). Concretely this means strong identity for every user and workload (mTLS, short-lived credentials instead of static secrets in config), per-request policy enforcement, least privilege everywhere, and audit logging. Google's BeyondCorp, built after the 2009 Aurora intrusion, is the reference deployment: employees access internal apps from untrusted networks with no VPN, access decided per request from user identity and device posture. In system design answers, zero trust shows up as: no service trusts a caller just because it is inside the VPC; internal APIs authenticate with mTLS or signed tokens, and secrets live in a vault, not environment files.

Key points

  • Authn proves identity, authz enforces permissions per resource server-side; broken access control (IDOR) tops OWASP because authz is the part teams skip.
  • Sessions are stateful and instantly revocable; JWTs are stateless and fast to verify but hard to revoke, so pair short-lived access tokens (5-15 min) with revocable refresh tokens.
  • OAuth2 is delegated authorization (know the authorization code + PKCE flow); OIDC adds the identity layer that makes 'Sign in with Google' authentication.
  • TLS 1.3 everywhere in transit, mTLS for service-to-service identity; envelope encryption (DEK wrapped by KMS-held KEK) at rest, enabling rotation and crypto-shredding.
  • SQL injection dies to parameterized queries; XSS to output encoding and CSP; DDoS to edge/CDN absorption plus rate limiting on expensive endpoints.
  • Zero trust (BeyondCorp): authenticate and authorize every request regardless of network location; no implicit trust for being inside the VPC.

Tradeoffs

Server-side sessions

Pros

  • + Instant revocation and logout; full server control over active sessions
  • + Opaque IDs leak nothing if intercepted
  • + Simple, battle-tested model for browser apps with cookies

Cons

  • Session-store lookup on every request; the store is shared infrastructure to scale and replicate
  • Awkward across many independent services without centralizing auth
  • Cookies need CSRF defenses (SameSite, tokens)

JWT access tokens

Pros

  • + Stateless verification with a public key; no per-request store lookup, natural fit for microservices
  • + Carries claims (roles, tenant) so services avoid extra identity calls
  • + Standardized, cross-language ecosystem

Cons

  • No revocation until expiry; stolen tokens work until TTL runs out
  • Refresh-token machinery and denylists reintroduce state you tried to avoid
  • Algorithm confusion and weak validation are recurring real-world vulnerabilities

TLS termination at the edge vs end-to-end mTLS

Pros

  • + Edge termination centralizes certificates, offloads CPU, simplifies debugging
  • + mTLS end-to-end gives every service cryptographic identity and satisfies zero-trust internal networks

Cons

  • Edge termination leaves plaintext on the internal network, an implicit-trust assumption
  • mTLS everywhere is heavy certificate lifecycle work without a service mesh to automate it

In the interview

  • Say 'authentication then authorization, enforced server-side per resource' early, and mention IDOR as the failure mode; it signals you know where real bugs live.
  • When you choose JWTs, immediately state the revocation weakness and the short-TTL-plus-refresh-token mitigation before being asked.
  • Distinguish OAuth2 (authorization) from OIDC (authentication); confusing them is a known trap.
  • For DDoS, lead with 'absorb volumetric at the edge (CDN/Shield), rate limit the application layer' rather than trying to solve it in your own servers.

Related topics