Loading...
Loading...
Every line of code is an attack surface. Learn to write defensively.
Module 10 · Browser Internals & Best Practices
Production JavaScript requires more than syntax. The event loop, rendering pipeline, bundlers, debugging techniques, performance optimization, design patterns, testing, and security practices separate hobby scripts from professional software.
Learn Security Best Practices in JavaScript — covering XSS, Input Validation, Authentication, Data Exposure with interactive visual examples and step-by-step explanations.
Writing JavaScript that works in a tutorial is different from writing JavaScript that ships to production. The event loop explains why setTimeout(0) does not run immediately. Performance profiling finds bottlenecks before users do.
This module covers the internals and best practices that separate hobby scripts from professional software: debugging, bundlers, design patterns, testing strategies, and security fundamentals every developer should know.
Understanding security best practices is essential for every JavaScript developer. It shows up in frontend UI code, backend APIs, and framework internals — and getting it wrong leads to bugs that are hard to trace. This chapter builds intuition with visuals so the behavior sticks.
Cross-Site Scripting — the #1 web vulnerability.
// XSS: Attacker injects malicious scripts into your page. // Types: Stored, Reflected, DOM-based. // BAD — directly inserting user input: const name = getUserInput(); // "<script>steal(cookies)</script>" element.innerHTML = name; // EXECUTES the script! 💀 // BAD — template literal in innerHTML: element.innerHTML = `<h1>Hello, ${userInput}</h1>`; // XSS! // GOOD — use textContent (auto-escapes): element.textContent = name; // Displays as text, not HTML // GOOD — sanitize if HTML is needed: import DOMPurify from "dompurify"; element.innerHTML = DOMPurify.sanitize(userHtml);
XSS happens when user input is rendered as code. The fix: never trust user input, always escape or sanitize.
Validate on the server. Sanitize on the client. Trust nothing.
// RULE: Client validation = UX. Server validation = security. // Attackers bypass client-side checks trivially. // Server-side validation (with Zod): import { z } from "zod"; const UserSchema = z.object({ email: z.string().email().max(254), password: z.string().min(8).max(72), age: z.number().int().min(13).max(150), name: z.string().min(1).max(100).regex(/^[a-zA-Z\s]+$/), }); function createUser(input) { const result = UserSchema.safeParse(input); if (!result.success) { return { error: result.error.flatten() }; } // result.data is typed AND validated return saveToDb(result.data); } // SQL Injection prevention: // BAD — string concatenation: const query = `SELECT * FROM users WHERE id = ${userId}`; // userId = "1; DROP TABLE users;" 💀 // GOOD — parameterized queries: const result = await db.query( "SELECT * FROM users WHERE id = $1", [userId] // safely escaped by the driver ); // Path traversal prevention: // BAD: const file = req.params.filename; // "../../etc/passwd" fs.readFile(`./uploads/${file}`); // reads system files! // GOOD: const safeName = path.basename(req.params.filename); const filePath = path.join("./uploads", safeName);
Prove identity securely. Protect sessions.
// Password storage — NEVER store plaintext: import bcrypt from "bcrypt"; // Hash on signup: const hash = await bcrypt.hash(password, 12); // 12 rounds await db.insert({ email, passwordHash: hash }); // Verify on login: const user = await db.findByEmail(email); const valid = await bcrypt.compare(password, user.passwordHash); if (!valid) throw new Error("Invalid credentials"); // JWT best practices: // - Short expiration (15min for access tokens) // - Store in httpOnly cookies (not localStorage!) // - Include only necessary claims (no passwords) // - Use refresh tokens for longer sessions // CSRF protection: // Attacker tricks user's browser into making requests: // <img src="https://bank.com/transfer?to=attacker&amount=1000"> // Defenses: // 1. SameSite cookies (Lax or Strict): // Set-Cookie: session=abc; SameSite=Lax; Secure; HttpOnly // // 2. CSRF tokens — unique per session: // Server sends token → client includes in requests // Server verifies token matches session // // 3. Check Origin/Referer headers // Rate limiting — prevent brute force: // Allow 5 login attempts per 15 minutes per IP // After that: exponential backoff or CAPTCHA
Don't leak secrets. Encrypt sensitive data.
// Environment variables — keep secrets out of code: // .env (NEVER commit this file): // DATABASE_URL=postgres://user:pass@host/db // API_SECRET=sk_live_abc123 // Access in code: const secret = process.env.API_SECRET; // .gitignore: // .env // .env.local // .env*.local // HTTPS everywhere — encrypt data in transit: // Modern hosting (Vercel, Cloudflare) handles this // For custom servers: use Let's Encrypt certificates // Don't expose sensitive data in responses: // BAD: app.get("/api/user/:id", async (req, res) => { const user = await db.findById(req.params.id); res.json(user); // includes passwordHash, tokens, etc! }); // GOOD — pick only safe fields: app.get("/api/user/:id", async (req, res) => { const user = await db.findById(req.params.id); const { id, name, email, avatar } = user; res.json({ id, name, email, avatar }); }); // Client-side storage rules: // localStorage — NEVER for tokens/secrets (XSS accessible) // sessionStorage — same risk as localStorage // httpOnly cookies — safe from XSS (server-only access) // IndexedDB — encrypted if storing sensitive local data
Your code is only as secure as your weakest dependency.
// Audit dependencies regularly: npm audit // check for known vulnerabilities npm audit fix // auto-fix where possible pnpm audit // same for pnpm // Lock file — pin exact versions: // package-lock.json or pnpm-lock.yaml // ALWAYS commit lock files! // Without them: npm install may get different (vulnerable) versions // Evaluate before installing: // Ask: Do I really need this package? // Check: downloads, maintenance, open issues, last update // Tools: npms.io, snyk.io/advisor, bundlephobia.com // Supply chain attacks are real: // - Typosquatting: "lodsh" instead of "lodash" // - Maintainer account compromise // - Malicious postinstall scripts // Defenses: // 1. Use exact versions: "lodash": "4.17.21" (not "^4.17.21") // 2. Review lock file diffs in PRs // 3. Use npm/pnpm overrides for transitive dependency fixes: { "pnpm": { "overrides": { "vulnerable-pkg": ">=2.0.1" } } } // Automated tools: // - GitHub Dependabot (auto-PRs for updates) // - Snyk (continuous monitoring) // - Socket.dev (detect supply chain risks)
Common questions about security best practices.