Front-end security - XSS and CSRF prevention

Alex Chang Feb 2026
1 tab
// 1. DANGEROUS: Never use innerHTML with user input
const userInput = '<img src=x onerror="alert('XSS')">';

// WRONG - vulnerable to XSS
document.getElementById('output').innerHTML = userInput;

// RIGHT - safe, treats as text
document.getElementById('output').textContent = userInput;

// 2. Safe DOM manipulation
function displayUserComment(comment) {
  const commentEl = document.createElement('div');
  commentEl.className = 'comment';

  // Create text nodes (safe)
  const authorEl = document.createElement('strong');
  authorEl.textContent = comment.author; // Safe

  const textEl = document.createElement('p');
  textEl.textContent = comment.text; // Safe

  commentEl.appendChild(authorEl);
  commentEl.appendChild(textEl);

  document.getElementById('comments').appendChild(commentEl);
}

// 3. Sanitize HTML with DOMPurify
import DOMPurify from 'dompurify';

function displayRichContent(html) {
  const clean = DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'],
    ALLOWED_ATTR: ['href'],
  });

  document.getElementById('content').innerHTML = clean;
}

// Usage
const userHTML = '<p>Hello <script>alert("XSS")</script></p>';
displayRichContent(userHTML); // Script tag removed

// 4. Escape user input for HTML context
function escapeHtml(unsafe) {
  return unsafe
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#039;');
}

// Usage
const userText = '<script>alert("XSS")</script>';
const safe = escapeHtml(userText);
// Result: &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;

// 5. Validate and sanitize URLs
function isSafeUrl(url) {
  try {
    const parsed = new URL(url);
    // Only allow http/https
    return ['http:', 'https:'].includes(parsed.protocol);
  } catch {
    return false;
  }
}

function createSafeLink(url, text) {
  if (!isSafeUrl(url)) {
    console.warn('Unsafe URL blocked:', url);
    return document.createTextNode(text);
  }

  const link = document.createElement('a');
  link.href = url;
  link.textContent = text;
  link.rel = 'noopener noreferrer'; // Security: prevent window.opener access

  return link;
}

// 6. CSRF protection with tokens
async function submitForm(data) {
  // Get CSRF token from meta tag
  const token = document.querySelector('meta[name="csrf-token"]')?.content;

  if (!token) {
    throw new Error('CSRF token not found');
  }

  const response = await fetch('/api/submit', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-Token': token,
    },
    credentials: 'same-origin', // Include cookies
    body: JSON.stringify(data),
  });

  return response.json();
}

// HTML: <meta name="csrf-token" content="<%= csrf_token %>">

// 7. Input validation
function validateEmail(email) {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return regex.test(email);
}

function validateUsername(username) {
  // Only alphanumeric and underscore, 3-20 characters
  const regex = /^[a-zA-Z0-9_]{3,20}$/;
  return regex.test(username);
}

function sanitizeInput(input, maxLength = 100) {
  // Remove control characters and trim
  return input
    .replace(/[\x00-\x1F\x7F]/g, '')
    .trim()
    .slice(0, maxLength);
}

// 8. Prevent clickjacking
// Server-side header: X-Frame-Options: DENY
// Or use CSP: Content-Security-Policy: frame-ancestors 'none'

// Client-side check
if (window.top !== window.self) {
  // Page is in an iframe
  document.body.innerHTML = 'This page cannot be displayed in an iframe';
}

// 9. Secure cookie handling
// Server sets HTTP-only cookies
// Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Strict

// Client can't access HTTP-only cookies
// But can read non-HTTP-only cookies
function getCookie(name) {
  const cookies = document.cookie.split(';');
  for (let cookie of cookies) {
    const [key, value] = cookie.trim().split('=');
    if (key === name) {
      return decodeURIComponent(value);
    }
  }
  return null;
}

// 10. Content Security Policy (CSP)
/*
Server-side header:
Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://trusted-cdn.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  font-src 'self' https://fonts.gstatic.com;
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';
*/

// 11. Subresource Integrity (SRI)
/*
<script
  src="https://cdn.example.com/library.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
  crossorigin="anonymous">
</script>
*/

// 12. Rate limiting on client
class RateLimiter {
  constructor(maxRequests, windowMs) {
    this.maxRequests = maxRequests;
    this.windowMs = windowMs;
    this.requests = [];
  }

  canMakeRequest() {
    const now = Date.now();
    this.requests = this.requests.filter(time => now - time < this.windowMs);

    if (this.requests.length < this.maxRequests) {
      this.requests.push(now);
      return true;
    }

    return false;
  }
}

// Usage
const limiter = new RateLimiter(5, 60000); // 5 requests per minute

async function makeApiCall() {
  if (!limiter.canMakeRequest()) {
    alert('Too many requests. Please wait.');
    return;
  }

  await fetch('/api/data');
}
1 file · javascript Explain with highlit

Front-end security protects users from malicious attacks. I prevent Cross-Site Scripting (XSS) by sanitizing user input and using textContent instead of innerHTML. Content Security Policy (CSP) headers restrict resource loading to trusted sources. Cross-Site Request Forgery (CSRF) tokens validate form submissions. HTTP-only cookies prevent JavaScript access to sensitive data. Input validation checks data on both client and server. The DOMPurify library safely sanitizes HTML. Escaping user input prevents script injection. HTTPS ensures encrypted data transmission. Understanding security vulnerabilities protects user data and builds trust.