← Back to the Build Your Homepage series
🔒
EPISODE 01
Stored · reflected · DOM · escaping · CSP · SameSite cookies · tokens · JWT · session · Prepared statements · ORM · input validation · TLS · env vars · .env · CSP · HSTS · helmet

XSS — Cross-Site Scripting · CSRF & Authentication · SQL Injection · HTTPS, Secrets & Headers

XSS lets attackers inject scripts that run in your users' browsers. Learn the three flavors, how to escape output, and how Content Security Policy gives defense in depth. Protect authenticated endpoints from CSRF. Use SameSite cookies, CSRF tokens, and understand the trade-offs between session cookies and JWT tokens. SQL injection is decades old and still happening. Always use prepared statements, validate at boundaries, and treat ORM queries with the same care. Encrypt traffic with HTTPS, keep secrets out of git, and harden HTTP responses with the right headers (CSP, HSTS, X-Frame-Options).

XSSsecurityCSPescapingCSRFauthJWTSameSitecookiesSQL injectionprepared statementsORMvalidationHTTPSTLS.envheadershelmetHSTS
Duration
⏱ About 1.5 hours + About 2 hours + About 1.5 hours + About 1.5 hours
Level
📊 Intermediate
Prerequisite
🎯 JavaScript track
OUTCOME
Render any user content safely; ship a basic CSP / An auth system that resists CSRF and common token misuse / Write database queries that are impossible to inject / Production-grade headers and zero secrets in source control

What you'll learn

  • 1Distinguish stored, reflected, and DOM XSS
  • 2Always escape output (default in React/Vue/Angular)
  • 3Avoid innerHTML / dangerouslySetInnerHTML
  • 4Configure a Content Security Policy
  • 5Explain CSRF in one sentence
  • 6Block CSRF with SameSite=Lax/Strict cookies
  • 7Use CSRF tokens when SameSite is not enough
  • 8Compare session cookies vs JWT (and pick session for new apps)
  • 9Recognize a vulnerable query in 5 seconds
  • 10Always use parameterized queries
  • 11Validate input at the API boundary
  • 12Use ORMs safely (no raw template strings)
  • 13Serve every page over HTTPS
  • 14Use environment variables for secrets, never commit .env
  • 15Apply HSTS to force HTTPS
  • 16Use helmet in Express for sensible defaults

XSS — Cross-Site Scripting

XSS lets attackers inject scripts that run in your users' browsers. Learn the three flavors, how to escape output, and how Content Security Policy gives defense in depth.

1. The Three Flavors

  • Stored — payload saved on the server (e.g., in a comment) and served to other users
  • Reflected — payload in a URL/query that the server reflects back into HTML
  • DOM — JavaScript on the client builds HTML from untrusted input

2. Always Escape Output

javascript
// VULNERABLE
el.innerHTML = `<p>Hello, ${userInput}</p>`;
// If userInput = '<img src=x onerror="alert(1)">' — game over

// SAFE
el.textContent = `Hello, ${userInput}`;
// Or React: {userInput} is auto-escaped
⚠️

Avoid React's dangerouslySetInnerHTML unless you sanitize input with a library like DOMPurify first.

3. Sanitize When You Must Render HTML

javascript
import DOMPurify from "dompurify";
const clean = DOMPurify.sanitize(richHtml);
el.innerHTML = clean;

4. Content Security Policy (CSP)

text
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; img-src 'self' data: https:

Even if an attacker injects a <script>, the browser refuses to run it unless it matches your CSP. Defense in depth.

CSRF & Authentication

Protect authenticated endpoints from CSRF. Use SameSite cookies, CSRF tokens, and understand the trade-offs between session cookies and JWT tokens.

1. What is CSRF?

An attacker tricks a logged-in user's browser into firing a request to your site, riding on their cookies. The user did not intend it.

html
<!-- attacker.com -->
<img src="https://yourbank.com/transfer?to=evil&amount=1000">

2. SameSite Cookies (First Line of Defense)

text
Set-Cookie: session=abc; HttpOnly; Secure; SameSite=Lax; Path=/
  • Lax — cookie not sent on cross-site POST (good default)
  • Strict — cookie not sent on any cross-site request
  • None — only with Secure; needed for embedded contexts

3. CSRF Tokens

Render a per-session random token in your form. The server requires it on submit. The attacker's site cannot read it (Same-Origin Policy).

javascript
// Express + csurf
import csurf from "csurf";
app.use(csurf({ cookie: true }));
app.get("/form", (req, res) => {
  res.render("form", { csrfToken: req.csrfToken() });
});
app.post("/transfer", (req, res) => { /* csurf validates automatically */ });

4. Session Cookies vs JWT

Session cookieJWT (Authorization header)
StorageServer-side (Redis/DB)Client-side (token contains data)
RevokeEasy (delete the row)Hard — must check blocklist
SizeTinyLarge (whole user info)
XSSHttpOnly cookie cannot be stolenlocalStorage tokens can be stolen
Best forMost web appsStateless APIs, mobile clients
💡

Default to session cookies for traditional web apps. JWT shines when you cannot rely on cookies (mobile, B2B APIs).

SQL Injection

SQL injection is decades old and still happening. Always use prepared statements, validate at boundaries, and treat ORM queries with the same care.

1. The Classic Example

javascript
// VULNERABLE — string concatenation
const sql = `SELECT * FROM users WHERE email = '${email}'`;
// email = "x' OR '1'='1" — returns ALL rows
// email = "x'; DROP TABLE users; --" — drops the table

2. Prepared Statements (Always)

javascript
// SAFE — placeholder + parameter
const stmt = db.prepare("SELECT * FROM users WHERE email = ?");
const user = stmt.get(email);

// PostgreSQL with pg
await pool.query("SELECT * FROM users WHERE email = $1", [email]);

// MySQL with mysql2
await pool.execute("SELECT * FROM users WHERE email = ?", [email]);
⚠️

Parameters are not string substitution — the driver sends them as separate data, so they can never be SQL.

3. ORMs

javascript
// Prisma — safe by design
await prisma.user.findUnique({ where: { email } });

// Raw query in Prisma — STILL safe with $queryRaw + tagged template
await prisma.$queryRaw`SELECT * FROM users WHERE email = ${email}`;

// DANGEROUS — string interpolation defeats the protection
await prisma.$queryRawUnsafe(`SELECT * FROM users WHERE email = '${email}'`);

4. Defense in Depth

  • Validate input shape (Zod, Joi) — reject obvious garbage at the boundary
  • Use least-privileged DB users (the app user should not be able to DROP tables)
  • Log and rate-limit suspicious queries
  • Run a SAST scanner in CI (e.g., Semgrep)

HTTPS, Secrets & Headers

Encrypt traffic with HTTPS, keep secrets out of git, and harden HTTP responses with the right headers (CSP, HSTS, X-Frame-Options).

1. HTTPS Everywhere

  • Vercel / Netlify give you HTTPS automatically
  • On a VPS, use Caddy or Certbot (Let's Encrypt) — free certificates
  • Set HSTS to force HTTPS on revisits
text
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

2. Secrets in .env

text
# .env (never committed)
DATABASE_URL=postgres://...
JWT_SECRET=...
STRIPE_KEY=...

# .gitignore
.env
.env.local
javascript
import "dotenv/config";
const dbUrl = process.env.DATABASE_URL;
⚠️

If you ever commit a secret, ROTATE it immediately. Removing the commit does not help — git history is forever.

3. Security Headers (Express + helmet)

javascript
import helmet from "helmet";
app.use(helmet());   // sane defaults: CSP, HSTS, frameguard, noSniff, etc.
HeaderEffect
Content-Security-PolicyLimits what scripts/styles/etc. can run
Strict-Transport-SecurityForce HTTPS
X-Content-Type-Options: nosniffStop MIME sniffing
X-Frame-Options: DENYPrevent clickjacking
Referrer-PolicyLimit Referer leaks

4. Final Checklist Before Shipping

  • HTTPS on every endpoint
  • Secrets in env vars, never in code
  • Strong password hashing (bcrypt, argon2)
  • Rate-limit login and signup endpoints
  • Log security events (failed logins, role changes)
  • Keep dependencies updated (npm audit)
Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub ↗

Comments 0

No comments yet. Be the first.

Only members can comment.

Comments with ads, abuse, personal information or illegal content are hidden or removed. Comments are shown with your nickname. Privacy Policy