Security

Encryption, input validation, least privilege, and the security principles every system design should consider.

Security Is Not an Afterthought

Security gets its own chapter here because it intersects with every other topic. Every layer — networking, databases, caching, communication — has security implications. Bolting on security after the fact is dramatically more expensive (and less effective) than building it in from the start.

Security is about trade-offs, not absolutes. A perfectly secure system that nobody can use is worthless. The goal is to make the cost of attacking your system exceed the value of what an attacker would gain. Focus your investment on protecting the most valuable and most exposed assets.

Encryption

Encryption in Transit

All data moving between services, between client and server, or across networks should be encrypted. This prevents eavesdropping, man-in-the-middle attacks, and data tampering.

Client
— TLS 1.3 →
Load Balancer
— mTLS →
Service
External traffic uses TLS. Internal service-to-service traffic uses mutual TLS (mTLS) where both sides verify each other's identity.
Encrypt everything in transit — external and internal.
  • TLS (HTTPS): Non-negotiable for any client-facing endpoint. Use TLS 1.3 where possible — it's faster (1-RTT handshake) and removes weak cipher suites.
  • mTLS: Mutual TLS for service-to-service communication. Both the client and server present certificates, so each side knows who it's talking to. Common in service mesh architectures (Istio, Linkerd).
  • Certificate management: Use automated certificate rotation (Let's Encrypt, AWS Certificate Manager). Manually managed certs will expire at the worst possible time.

Encryption at Rest

Data stored on disk — databases, file systems, backups — should be encrypted so that physical access to the storage medium doesn't expose the data.

  • Full-disk encryption: Encrypts the entire volume. Transparent to applications. Available on all major cloud providers by default.
  • Application-level encryption: Encrypt sensitive fields (SSN, credit card numbers) before writing to the database. Even if the database is compromised, the sensitive data remains encrypted.
  • Key management: Don't store encryption keys alongside the encrypted data. Use a dedicated key management service (AWS KMS, HashiCorp Vault) with strict access controls and audit logging.

Input Validation & Sanitization

Never trust input from outside your system boundary. Every piece of external data — user forms, API parameters, webhook payloads, file uploads — should be validated and sanitized.

Common Attack Vectors

Attack What happens Prevention
SQL Injection Attacker inserts SQL code via input fields Parameterized queries. Never concatenate user input into SQL strings.
XSS Attacker injects JavaScript into your pages Escape all user-generated content. Use CSP headers. Sanitize HTML input.
CSRF Attacker tricks users into making unintended requests CSRF tokens, SameSite cookies, verify Origin header.
Path Traversal Attacker accesses files outside intended directory Sanitize file paths. Never use user input directly in file system operations.
SSRF Attacker makes your server send requests to internal services Allowlist permitted URLs. Block requests to internal IP ranges.
Validate at the boundary, trust internally. Validate user input at your API layer. Once data passes validation and enters your system, internal services can trust it without re-validating. This keeps validation logic in one place and avoids redundant checks in every service.

Principle of Least Privilege

Every user, service, and process should have only the minimum permissions needed to do its job — and nothing more. If your API server only reads from the users table, its database credentials should only have SELECT on that table, not DROP TABLE on the entire database.

Applying Least Privilege

  • Service accounts: Each microservice gets its own credentials with narrowly scoped permissions. No shared "admin" accounts.
  • IAM roles: Use cloud IAM roles instead of long-lived API keys. Roles provide temporary credentials that auto-rotate.
  • Network segmentation: Services that don't need to talk to each other shouldn't be able to. Use security groups, network policies, or service mesh authorization.
  • Secrets management: Never hardcode secrets in code or config files. Use a secrets manager (Vault, AWS Secrets Manager, GCP Secret Manager) with audit trails.

Defense in Depth

No single security measure is foolproof. Defense in depth means layering multiple controls so that if one fails, others still protect you:

Edge: DDoS protection, WAF, rate limiting
Network: Firewalls, security groups, mTLS
Application: Auth, input validation, RBAC
Data: Encryption, access controls
Multiple layers of protection. Breaching one layer doesn't expose everything.

Authentication & Authorization

Authentication answers "who are you?" Authorization answers "what are you allowed to do?" They're separate concerns that are often conflated.

  • Authentication patterns: Session-based (server stores session state), JWT tokens (stateless, signed tokens that encode user identity), OAuth 2.0 (delegated authorization for third-party access).
  • Authorization patterns: RBAC (role-based — admin, editor, viewer), ABAC (attribute-based — more flexible rules like "user can edit documents they created"), ACL (access control lists — per-resource permissions).
JWTs are not session replacements. JWTs are stateless, which means you can't revoke them before they expire. If a user's account is compromised, you can't invalidate their JWT without maintaining a blocklist — at which point you've rebuilt server-side sessions. Use JWTs for short-lived, machine-to-machine tokens. Use sessions for user-facing authentication where revocability matters.

Rate Limiting

Rate limiting controls how many requests a client can make in a given time window. It protects against abuse, brute-force attacks, and accidental overload.

  • Fixed window: Allow N requests per minute. Simple but susceptible to bursts at window boundaries.
  • Sliding window: Smooths out bursts by tracking requests over a rolling time period.
  • Token bucket: Tokens are added at a fixed rate. Each request consumes a token. Allows controlled bursts while maintaining an average rate.
  • Leaky bucket: Requests are processed at a fixed rate. Excess requests queue up (or are dropped). Provides very consistent output rate.