Every web application is a target. Whether you run a personal blog or a banking platform, attackers probe for weaknesses automatically and at scale. Understanding why security matters — and how to think about it systematically — is the first step toward building resilient software.
Why Security Matters
A single vulnerability can lead to:
- Data breaches — leaked user credentials, payment info, or personal data.
- Financial loss — fraud, regulatory fines (GDPR penalties can reach 4% of annual revenue).
- Reputation damage — users lose trust quickly and rarely come back.
- Legal liability — negligence in handling user data can result in lawsuits.
Security is not a feature you bolt on at the end. It is a property of the entire system that must be considered from day one.
Threat Modeling
Before writing a single line of defensive code, identify what you are protecting and who might attack it.
A simple threat-modeling process:
- Identify assets — user data, API keys, session tokens, admin access.
- Identify entry points — forms, APIs, file uploads, third-party integrations.
- Identify threats — who would attack (script kiddies, competitors, insiders) and how.
- Rate risk — likelihood x impact. Focus on high-risk items first.
- Mitigate — apply controls (validation, encryption, access control).
A lightweight way to document this is a simple table:
// Example: threat model as structured data
const threats = [
{
asset: "User passwords",
entryPoint: "Login form",
threat: "Brute-force attack",
likelihood: "high",
impact: "high",
mitigation: "Rate limiting, account lockout, bcrypt hashing",
},
{
asset: "Session token",
entryPoint: "Cookies",
threat: "Session hijacking via XSS",
likelihood: "medium",
impact: "high",
mitigation: "HttpOnly cookies, CSP, input sanitization",
},
];The OWASP Top 10
The OWASP Top 10 is the industry-standard list of the most critical web application security risks. Here is the 2021 edition at a glance:
| # | Category | What It Covers |
|---|---|---|
| 1 | Broken Access Control | Users acting outside their permissions |
| 2 | Cryptographic Failures | Weak encryption, plaintext storage |
| 3 | Injection | SQL, NoSQL, OS, LDAP injection |
| 4 | Insecure Design | Missing security architecture |
| 5 | Security Misconfiguration | Default creds, open cloud storage |
| 6 | Vulnerable Components | Outdated libraries with known CVEs |
| 7 | Auth Failures | Broken authentication and session management |
| 8 | Software & Data Integrity | Untrusted deserialization, CI/CD tampering |
| 9 | Logging Failures | Missing audit trails |
| 10 | SSRF | Server-Side Request Forgery |
We will cover the most common of these in detail throughout this course.
Defense in Depth
No single control is enough. Defense in depth means layering multiple security mechanisms so that if one fails, others still protect the system.
// Layers of defense for a typical web app
const layers = [
"Input validation (client + server)",
"Parameterized queries (prevent injection)",
"Authentication & authorization checks",
"HTTPS everywhere (encrypt data in transit)",
"Security headers (CSP, HSTS, X-Frame-Options)",
"Rate limiting & abuse detection",
"Logging & monitoring (detect breaches early)",
"Regular dependency audits (patch known CVEs)",
];Think of it like a castle: the moat, the outer wall, the inner wall, and the keep each provide independent protection. An attacker must bypass all layers to reach the crown jewels.
Practical Example: Securing a Form Endpoint
import { rateLimit } from "@/lib/rate-limit";
import { sanitize } from "@/lib/sanitize";
import { db } from "@/lib/db";
export async function POST(request: Request) {
// Layer 1: Rate limiting
const ip = request.headers.get("x-forwarded-for") ?? "unknown";
if (await rateLimit(ip, { max: 10, windowMs: 60_000 })) {
return new Response("Too many requests", { status: 429 });
}
const body = await request.json();
// Layer 2: Input validation
const email = sanitize(body.email);
if (!email || !email.includes("@")) {
return new Response("Invalid email", { status: 400 });
}
// Layer 3: Parameterized query (prevent SQL injection)
await db.query("INSERT INTO subscribers (email) VALUES ($1)", [email]);
// Layer 4: Logging
console.log(`New subscriber: ${email} from ${ip}`);
return new Response("Subscribed", { status: 201 });
}Each layer handles a different class of threat. Remove any one of them and the endpoint is still partially protected by the others.
Key Takeaways
- Security is a continuous process, not a one-time checklist.
- Start with threat modeling to prioritize your effort.
- The OWASP Top 10 gives you a roadmap of the most common risks.
- Layer your defenses so no single failure is catastrophic.