Security

Encryption, checking input, giving each part only the access it needs, and the other security basics every design needs.

Security Is Not an Afterthought

Security gets its own chapter because it touches every other topic. Every layer has a security side: networking, databases, caching, and communication. Adding security at the end costs far more, and works less well, than building it in from the start.

Security is about trade-offs, not perfection. A system nobody can use is secure and useless. The goal is to make an attack cost more than the attacker would gain. Spend most of your effort on the parts that are most valuable and most exposed.

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 is 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 is 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 does not 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: Do not 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 do not need to talk to each other should not 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 does not expose everything.

Authentication & Authorization

Authentication answers "who are you?" Authorization answers "what are you allowed to do?" They are 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 cannot revoke them before they expire. If a user's account is compromised, you cannot invalidate their JWT without a blocklist. At that point you have 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.