Day 3 of 5
⏱ ~60 minutes
Auth and Security for Web Apps — Day 3

CSRF and XSS: The Two Most Common Web Vulnerabilities

Cross-Site Request Forgery and Cross-Site Scripting are responsible for a huge proportion of web security incidents. Day 3 covers what they are and exactly how to prevent them.

Cross-Site Request Forgery (CSRF)

CSRF tricks an authenticated user's browser into making an unwanted request to your app. If your API uses cookies for auth, an attacker can embed a request in an image or link that runs with the user's session.

CSRF Example — The Attack
<!-- Attacker's page sends a POST as the logged-in user -->
<img src="https://bank.com/transfer?to=attacker&amount=1000">

<!-- Or a form that auto-submits -->
<form method="POST" action="https://bank.com/transfer">
  <input name="to" value="attacker">
  <input name="amount" value="1000">
</form>
<script>document.forms[0].submit();</script>
CSRF Prevention
npm install csurf
// Note: for modern apps, use SameSite cookies instead

// Method 1: SameSite cookie attribute (preferred)
res.cookie('sessionId', token, {
  httpOnly: true,
  secure: true,           // HTTPS only
  sameSite: 'strict',     // Never sent cross-site
  maxAge: 15 * 60 * 1000  // 15 minutes
});

// Method 2: CSRF token in forms (for traditional web apps)
const csrf = require('csurf');
app.use(csrf({ cookie: true }));

app.get('/form', (req, res) => {
  res.render('form', { csrfToken: req.csrfToken() });
});

// In your HTML form:
// <input type="hidden" name="_csrf" value="<%= csrfToken %>">

Cross-Site Scripting (XSS)

XSS injects malicious scripts into pages viewed by other users. If you render user input without escaping it, attackers can steal sessions, redirect users, or deface your app.

XSS Prevention
npm install dompurify helmet

// 1. Never render user input as raw HTML
// WRONG:
element.innerHTML = userInput;
// RIGHT:
element.textContent = userInput; // Auto-escapes

// 2. If you MUST render HTML, sanitize it first
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userInput);

// 3. Content Security Policy headers (server-side)
const helmet = require('helmet');
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"],  // No inline scripts
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "data:", "https:"],
    }
  }
}));
Day 3 Exercise
Secure Your App Against CSRF and XSS
  1. Add SameSite=strict to all session cookies in your app.
  2. Add the helmet middleware to your Express app.
  3. Find every place in your frontend that sets innerHTML — replace with textContent or DOMPurify.
  4. Test your CSP by opening browser devtools and looking for CSP violations after adding the policy.

Day 3 Summary

  • CSRF prevention: use SameSite=strict cookies — this is the modern standard over CSRF tokens.
  • XSS prevention: never set innerHTML with user input. Use textContent or DOMPurify.
  • Helmet adds security headers including Content-Security-Policy — add it to every Express app.
Finished this lesson?