Skip to main content

Input Validation and Sanitization in JavaScript

Created: May 8, 2026 Larry Qu 7 min read

Input validation and sanitization are critical security practices. This article covers validation techniques, sanitization methods, and secure input handling.

Introduction

Input validation and sanitization provide:

  • Security protection
  • Data integrity
  • Error prevention
  • User experience
  • Compliance

Understanding these practices helps you:

  • Prevent security vulnerabilities
  • Validate user input
  • Sanitize dangerous content
  • Protect applications
  • Build secure systems

Input Validation

Basic Validation

// ✅ Good: Basic input validation
function validateEmail(email) {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
}

function validatePassword(password) {
  return password.length >= 8;
}

function validateUsername(username) {
  return /^[a-zA-Z0-9_]{3,20}$/.test(username);
}

// Usage
console.log(validateEmail('[email protected]')); // true
console.log(validateEmail('invalid')); // false
console.log(validatePassword('short')); // false
console.log(validatePassword('securePass123')); // true

Type Validation

// ✅ Good: Type validation
function validateType(value, expectedType) {
  switch (expectedType) {
    case 'string':
      return typeof value === 'string';
    case 'number':
      return typeof value === 'number' && !isNaN(value);
    case 'boolean':
      return typeof value === 'boolean';
    case 'array':
      return Array.isArray(value);
    case 'object':
      return value !== null && typeof value === 'object' && !Array.isArray(value);
    case 'email':
      return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
    case 'url':
      try {
        new URL(value);
        return true;
      } catch {
        return false;
      }
    default:
      return false;
  }
}

// Usage
console.log(validateType('hello', 'string')); // true
console.log(validateType(42, 'number')); // true
console.log(validateType('[email protected]', 'email')); // true
console.log(validateType('https://example.com', 'url')); // true

Range Validation

// ✅ Good: Range validation
function validateRange(value, min, max) {
  return value >= min && value <= max;
}

function validateLength(str, minLength, maxLength) {
  return str.length >= minLength && str.length <= maxLength;
}

function validateAge(age) {
  return validateRange(age, 0, 150);
}

// Usage
console.log(validateRange(50, 0, 100)); // true
console.log(validateLength('hello', 3, 10)); // true
console.log(validateAge(25)); // true
console.log(validateAge(200)); // false

Comprehensive Validator

// ✅ Good: Comprehensive validator
class Validator {
  static rules = {
    required: (value) => value !== null && value !== undefined && value !== '',
    email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
    minLength: (value, min) => value.length >= min,
    maxLength: (value, max) => value.length <= max,
    pattern: (value, pattern) => pattern.test(value),
    number: (value) => !isNaN(value) && typeof value === 'number',
    url: (value) => {
      try {
        new URL(value);
        return true;
      } catch {
        return false;
      }
    }
  };

  static validate(data, schema) {
    const errors = {};

    for (const [field, rules] of Object.entries(schema)) {
      const value = data[field];

      for (const [ruleName, ruleValue] of Object.entries(rules)) {
        const rule = this.rules[ruleName];

        if (!rule) {
          console.warn(`Unknown rule: ${ruleName}`);
          continue;
        }

        const isValid = Array.isArray(ruleValue)
          ? rule(value, ...ruleValue)
          : rule(value, ruleValue);

        if (!isValid) {
          if (!errors[field]) {
            errors[field] = [];
          }
          errors[field].push(ruleName);
        }
      }
    }

    return {
      valid: Object.keys(errors).length === 0,
      errors
    };
  }
}

// Usage
const schema = {
  email: { required: true, email: true },
  password: { required: true, minLength: [8] },
  username: { required: true, minLength: [3], maxLength: [20] }
};

const data = {
  email: '[email protected]',
  password: 'securePass123',
  username: 'john_doe'
};

const result = Validator.validate(data, schema);
console.log(result); // { valid: true, errors: {} }

Input Sanitization

HTML Sanitization

// ✅ Good: HTML sanitization
function sanitizeHTML(html) {
  const div = document.createElement('div');
  div.textContent = html;
  return div.innerHTML;
}

function escapeHTML(text) {
  const map = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#039;'
  };
  return text.replace(/[&<>"']/g, (char) => map[char]);
}

// Usage
const userInput = '<script>alert("XSS")</script>';
console.log(sanitizeHTML(userInput));
// &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;

console.log(escapeHTML(userInput));
// &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;

String Sanitization

// ✅ Good: String sanitization
function sanitizeString(str) {
  return str
    .trim()
    .replace(/\s+/g, ' ')
    .replace(/[<>]/g, '');
}

function removeSpecialChars(str) {
  return str.replace(/[^a-zA-Z0-9\s]/g, '');
}

function sanitizeFilename(filename) {
  return filename
    .replace(/[^a-zA-Z0-9._-]/g, '')
    .substring(0, 255);
}

// Usage
console.log(sanitizeString('  hello   world  ')); // 'hello world'
console.log(removeSpecialChars('hello@world!')); // 'helloworld'
console.log(sanitizeFilename('my<file>.txt')); // 'myfile.txt'

URL Sanitization

// ✅ Good: URL sanitization
function sanitizeURL(url) {
  try {
    const parsed = new URL(url);
    // Only allow http and https
    if (!['http:', 'https:'].includes(parsed.protocol)) {
      return null;
    }
    return parsed.toString();
  } catch {
    return null;
  }
}

function sanitizeQueryParam(param) {
  return encodeURIComponent(param);
}

// Usage
console.log(sanitizeURL('https://example.com')); // 'https://example.com/'
console.log(sanitizeURL('javascript:alert("XSS")')); // null
console.log(sanitizeQueryParam('hello world')); // 'hello%20world'

XSS Prevention

Content Security Policy

// ✅ Good: CSP headers (server-side)
// Set-Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'

// Client-side: Use textContent instead of innerHTML
function displayUserContent(content) {
  const element = document.getElementById('content');
  element.textContent = content; // Safe
  // NOT: element.innerHTML = content; // Unsafe
}

// Usage
const userInput = '<script>alert("XSS")</script>';
displayUserContent(userInput); // Displays as text, not executed

DOM-based XSS Prevention

// ✅ Good: Prevent DOM-based XSS
function safeSetHTML(element, html) {
  // Use DOMPurify library for production
  const sanitized = sanitizeHTML(html);
  element.innerHTML = sanitized;
}

function safeSetAttribute(element, attr, value) {
  // Validate attribute
  const allowedAttrs = ['href', 'src', 'alt', 'title'];
  if (!allowedAttrs.includes(attr)) {
    console.warn(`Attribute ${attr} not allowed`);
    return;
  }

  // Sanitize value
  if (attr === 'href' || attr === 'src') {
    const sanitized = sanitizeURL(value);
    if (sanitized) {
      element.setAttribute(attr, sanitized);
    }
  } else {
    element.setAttribute(attr, escapeHTML(value));
  }
}

// Usage
const div = document.createElement('div');
safeSetHTML(div, '<p>Hello</p>');
safeSetAttribute(div, 'href', 'https://example.com');

SQL Injection Prevention

Parameterized Queries

// ✅ Good: Use parameterized queries
// With a database library like mysql2/promise

async function getUserByEmail(email) {
  // Parameterized query - safe from SQL injection
  const query = 'SELECT * FROM users WHERE email = ?';
  const [rows] = await connection.execute(query, [email]);
  return rows[0];
}

// ❌ Bad: String concatenation - vulnerable
async function getUserByEmailUnsafe(email) {
  const query = `SELECT * FROM users WHERE email = '${email}'`;
  // Vulnerable to SQL injection!
}

// Usage
const user = await getUserByEmail('[email protected]');

Input Validation for Queries

// ✅ Good: Validate before querying
function validateQueryInput(input) {
  // Remove dangerous characters
  return input
    .replace(/[;'"]/g, '')
    .trim()
    .substring(0, 100);
}

async function searchUsers(searchTerm) {
  const validated = validateQueryInput(searchTerm);
  const query = 'SELECT * FROM users WHERE name LIKE ?';
  const [rows] = await connection.execute(query, [`%${validated}%`]);
  return rows;
}

// Usage
const results = await searchUsers("O'Brien"); // Safe

CSRF Prevention

Token-based CSRF Protection

// ✅ Good: CSRF token validation
class CSRFProtection {
  static generateToken() {
    return Math.random().toString(36).substring(2, 15) +
           Math.random().toString(36).substring(2, 15);
  }

  static validateToken(token, sessionToken) {
    return token === sessionToken;
  }

  static addTokenToForm(form, token) {
    const input = document.createElement('input');
    input.type = 'hidden';
    input.name = 'csrf_token';
    input.value = token;
    form.appendChild(input);
  }
}

// Usage
const token = CSRFProtection.generateToken();
const form = document.getElementById('myForm');
CSRFProtection.addTokenToForm(form, token);
// ✅ Good: Set SameSite cookie attribute (server-side)
// Set-Cookie: sessionId=abc123; SameSite=Strict; Secure; HttpOnly

// Client-side: Use fetch with credentials
async function makeRequest(url, data) {
  const response = await fetch(url, {
    method: 'POST',
    credentials: 'include', // Include cookies
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-Token': getCsrfToken()
    },
    body: JSON.stringify(data)
  });
  return response.json();
}

function getCsrfToken() {
  return document.querySelector('meta[name="csrf-token"]').content;
}

Practical Security Examples

Secure Form Handler

// ✅ Good: Secure form handler
class SecureFormHandler {
  constructor(formId, schema) {
    this.form = document.getElementById(formId);
    this.schema = schema;
    this.setupEventListeners();
  }

  setupEventListeners() {
    this.form.addEventListener('submit', (e) => this.handleSubmit(e));
  }

  handleSubmit(e) {
    e.preventDefault();

    const formData = new FormData(this.form);
    const data = Object.fromEntries(formData);

    // Validate
    const validation = Validator.validate(data, this.schema);
    if (!validation.valid) {
      this.displayErrors(validation.errors);
      return;
    }

    // Sanitize
    const sanitized = this.sanitizeData(data);

    // Submit
    this.submitForm(sanitized);
  }

  sanitizeData(data) {
    const sanitized = {};
    for (const [key, value] of Object.entries(data)) {
      if (typeof value === 'string') {
        sanitized[key] = escapeHTML(value.trim());
      } else {
        sanitized[key] = value;
      }
    }
    return sanitized;
  }

  displayErrors(errors) {
    for (const [field, messages] of Object.entries(errors)) {
      const input = this.form.querySelector(`[name="${field}"]`);
      if (input) {
        input.classList.add('error');
        input.title = messages.join(', ');
      }
    }
  }

  async submitForm(data) {
    try {
      const response = await fetch(this.form.action, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-CSRF-Token': getCsrfToken()
        },
        body: JSON.stringify(data)
      });

      if (response.ok) {
        console.log('Form submitted successfully');
      } else {
        console.error('Form submission failed');
      }
    } catch (error) {
      console.error('Error submitting form:', error);
    }
  }
}

// Usage
const schema = {
  email: { required: true, email: true },
  password: { required: true, minLength: [8] }
};

new SecureFormHandler('loginForm', schema);

Best Practices

  1. Always validate on server:
    // ✅ Good - validate on both client and server
    // Client: Quick feedback
    // Server: Security
    
    // ❌ Bad - only client validation
    ```javascript
    
  2. Use allowlists, not blocklists:
    // ✅ Good
    const allowedTags = ['p', 'br', 'strong', 'em'];
    
    // ❌ Bad
    const blockedTags = ['script', 'iframe'];
    ```javascript
    
  3. Escape output:
    // ✅ Good
    element.textContent = userInput;
    
    // ❌ Bad
    element.innerHTML = userInput;
    ```javascript
    

Common Mistakes

  1. Client-side validation only:
    // ❌ Bad - can be bypassed
    if (validateEmail(email)) {
      submitForm();
    }
    
    // ✅ Good - validate on server too
    ```javascript
    
  2. Using innerHTML with user input:
    // ❌ Bad
    element.innerHTML = userInput;
    
    // ✅ Good
    element.textContent = userInput;
    ```javascript
    
  3. Not escaping output:
    // ❌ Bad
    const html = `<p>${userInput}</p>`;
    
    // ✅ Good
    const html = `<p>${escapeHTML(userInput)}</p>`;
    

Summary

Input validation and sanitization are essential security practices. Key takeaways:

  • Validate all input
  • Sanitize dangerous content
  • Prevent XSS attacks
  • Prevent SQL injection
  • Prevent CSRF attacks
  • Use allowlists
  • Escape output
  • Validate server-side

Next Steps

Resources

Comments

Share this article

Scan to read on mobile