Skip to content

Input Validation Best Practices

Alex Stojcic edited this page Apr 3, 2025 · 1 revision

Input Validation Best Practices

Input validation is a critical security practice that involves verifying that data entering your application meets expected criteria before processing it. Proper input validation helps prevent a wide range of security vulnerabilities, including injection attacks, cross-site scripting (XSS), and other common attack vectors.

Why Input Validation Matters

Unvalidated input is one of the most common sources of security vulnerabilities. Consider these scenarios:

  • An application that accepts SQL queries directly from user input could be vulnerable to SQL injection
  • A web form that renders user input without validation could enable cross-site scripting attacks
  • An API that accepts unlimited file uploads without type checking could allow malicious file execution

Implementing robust input validation significantly reduces these risks.

Input Validation Strategies

1. Whitelist vs. Blacklist Validation

Approach Description Recommendation
Whitelist Accept only known good input Preferred approach - more secure
Blacklist Reject known bad input Less secure - attackers can bypass

Example of Whitelist Validation (JavaScript):

// Whitelist validation - only accept alphanumeric usernames
function validateUsername(username) {
  const alphanumericRegex = /^[a-zA-Z0-9]+$/;
  return alphanumericRegex.test(username);
}

// Usage if (!validateUsername(userInput)) { return res.status(400).json({ error: 'Username must contain only letters and numbers' }); }

Blacklist Validation (Less Secure):

// Blacklist approach - try to block known bad characters
function validateInput(input) {
  // This approach is incomplete and vulnerable to bypass
  return !input.includes('<script>') && !input.includes('DROP TABLE');
}

2. Server-Side vs. Client-Side Validation

Always implement validation on both sides:

Client-Side Validation:

  • Provides immediate feedback to users
  • Reduces server load
  • Improves user experience
  • Cannot be trusted for security

Server-Side Validation:

  • Critical security control
  • Cannot be bypassed by malicious users
  • Must be implemented even if client-side validation exists

Validation Techniques by Data Type

String Validation

  1. Length constraints:
// Validate string length
function validateLength(input, min, max) {
  return input.length >= min && input.length <= max;
}

// Usage if (!validateLength(password, 8, 128)) { return res.status(400).json({ error: 'Password must be between 8 and 128 characters' }); }

  1. Character set restrictions:
// Validate email format
function validateEmail(email) {
  const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
  return emailRegex.test(email);
}

// Validate alphanumeric with specific special characters function validatePassword(password) { const passwordRegex = /^[a-zA-Z0-9!@#$%^&*()-_=+]+$/; return passwordRegex.test(password); }

Numeric Validation

// Validate integer range
function validateInteger(value, min, max) {
  const num = parseInt(value, 10);
  return !isNaN(num) && Number.isInteger(num) && num >= min && num <= max;
}

// Validate decimal/float function validateDecimal(value, min, max, precision) { const num = parseFloat(value); if (isNaN(num) || num < min || num > max) return false;

// Check decimal precision const decimalPart = value.toString().split('.')[1] || ''; return decimalPart.length <= precision; }

Date Validation

// Validate date format and range
function validateDate(dateString, minDate, maxDate) {
  const date = new Date(dateString);

// Check if date is valid if (isNaN(date.getTime())) return false;

// Check range if (minDate && date < new Date(minDate)) return false; if (maxDate && date > new Date(maxDate)) return false;

return true; }

File Validation

// Validate file type and size
function validateFile(file, allowedTypes, maxSizeInBytes) {
  // Check file type
  if (!allowedTypes.includes(file.mimetype)) {
    return false;
  }

// Check file size if (file.size > maxSizeInBytes) { return false; }

return true; }

// Usage app.post('/upload', upload.single('document'), (req, res) => { if (!validateFile( req.file, ['application/pdf', 'image/jpeg', 'image/png'], 5 * 1024 * 1024 // 5MB )) { return res.status(400).json({ error: 'Invalid file type or size' }); }

// Process valid file });

Framework-Specific Validation

Express.js (Node.js) with express-validator

const { body, validationResult } = require('express-validator');

app.post('/register', // Validation middleware [ body('username') .isAlphanumeric() .isLength({ min: 3, max: 20 }) .withMessage('Username must be 3-20 alphanumeric characters'),

body('email')
  .isEmail()
  .normalizeEmail()
  .withMessage('Must provide a valid email'),

body('password')
  .isLength({ min: 8 })
  .matches(/[a-z]/).withMessage('Password must include lowercase letter')
  .matches(/[A-Z]/).withMessage('Password must include uppercase letter')
  .matches(/[0-9]/).withMessage('Password must include a number')
  .matches(/[^a-zA-Z0-9]/).withMessage('Password must include a special character')

], // Handler (req, res) => { // Check for validation errors const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); }

// Process valid input
// ...

} );

Django (Python)

from django import forms

class UserRegistrationForm(forms.Form): username = forms.CharField( min_length=3, max_length=20, validators=[ RegexValidator( regex=r'^[a-zA-Z0-9]+$', message='Username must contain only letters and numbers' ) ] ) email = forms.EmailField() password = forms.CharField( min_length=8, widget=forms.PasswordInput, validators=[ RegexValidator( regex=r'^(?=.[a-z])(?=.[A-Z])(?=.\d)(?=.[@$!%?&])[A-Za-z\d@$!%?&]+$', message='Password must include uppercase, lowercase, number and special character' ) ] )

Sanitization Strategies

Validation should be paired with sanitization to ensure safe use of data.

HTML/XSS Sanitization

// Using DOMPurify library
const DOMPurify = require('dompurify');

function sanitizeHtml(input) { return DOMPurify.sanitize(input, { ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'], ALLOWED_ATTR: ['href', 'target'] }); }

// Using with user input that will be displayed as HTML const userComment = sanitizeHtml(req.body.comment);

SQL Injection Prevention

// Using parameterized queries with PostgreSQL
const { Pool } = require('pg');
const pool = new Pool();

async function getUser(username) { // Safe: Using parameterized query const result = await pool.query( 'SELECT id, name, email FROM users WHERE username = $1', [username] );

return result.rows[0]; }

Common Validation Vulnerabilities and Mitigations

1. Cross-Site Scripting (XSS)

Vulnerability:

// Unsafe: Directly rendering user input
app.get('/profile', (req, res) => {
  res.send(`<h1>Welcome, ${req.query.name}!</h1>`);
});

Mitigation:

const escapeHtml = require('escape-html');

app.get('/profile', (req, res) => { // Escape HTML special characters const safeName = escapeHtml(req.query.name || ''); res.send(&lt;h1&gt;Welcome, ${safeName}!&lt;/h1&gt;); });

2. SQL Injection

Vulnerability:

// Unsafe: Constructing SQL with string concatenation
function searchUsers(searchTerm) {
  return db.query(`SELECT * FROM users WHERE username LIKE '%${searchTerm}%'`);
}

Mitigation:

// Safe: Using parameterized queries
function searchUsers(searchTerm) {
  return db.query(
    'SELECT * FROM users WHERE username LIKE $1',
    [`%${searchTerm}%`]
  );
}

3. Mass Assignment / Parameter Pollution

Vulnerability:

// Unsafe: Directly using request body to update records
app.post('/users/:id', (req, res) => {
  const userId = req.params.id;
  db.updateUser(userId, req.body);
});

Mitigation:

// Safe: Explicitly selecting allowed fields
app.post('/users/:id', (req, res) => {
  const userId = req.params.id;

// Only allow specific fields to be updated const allowedUpdates = { name: req.body.name, email: req.body.email, bio: req.body.bio };

db.updateUser(userId, allowedUpdates); });

Input Validation Checklist

  • Implement server-side validation for ALL user inputs
  • Use whitelist validation rather than blacklist
  • Validate data type, format, length, and range
  • Sanitize input before use in HTML, SQL, or other contexts
  • Use parameterized queries for database operations
  • Implement context-specific encoding for different output locations
  • Add client-side validation for better user experience
  • Log validation failures for security monitoring
  • Use appropriate HTTP status codes for validation errors (400 Bad Request)
  • Don't reveal too much information in validation error messages

Additional Resources

# Input Validation Best Practices

Input validation is a critical security practice that involves verifying that data entering your application meets expected criteria before processing it. Proper input validation helps prevent a wide range of security vulnerabilities, including injection attacks, cross-site scripting (XSS), and other common attack vectors.

Why Input Validation Matters

Unvalidated input is one of the most common sources of security vulnerabilities. Consider these scenarios:

  • An application that accepts SQL queries directly from user input could be vulnerable to SQL injection
  • A web form that renders user input without validation could enable cross-site scripting attacks
  • An API that accepts unlimited file uploads without type checking could allow malicious file execution

Implementing robust input validation significantly reduces these risks.

Input Validation Strategies

1. Whitelist vs. Blacklist Validation

Approach Description Recommendation
Whitelist Accept only known good input Preferred approach - more secure
Blacklist Reject known bad input Less secure - attackers can bypass

Example of Whitelist Validation (JavaScript):

// Whitelist validation - only accept alphanumeric usernames
function validateUsername(username) {
  const alphanumericRegex = /^[a-zA-Z0-9]+$/;
  return alphanumericRegex.test(username);
}

// Usage
if (!validateUsername(userInput)) {
  return res.status(400).json({ error: 'Username must contain only letters and numbers' });
}

Blacklist Validation (Less Secure):

// Blacklist approach - try to block known bad characters
function validateInput(input) {
  // This approach is incomplete and vulnerable to bypass
  return !input.includes('<script>') && !input.includes('DROP TABLE');
}

2. Server-Side vs. Client-Side Validation

Always implement validation on both sides:

Client-Side Validation:

  • Provides immediate feedback to users
  • Reduces server load
  • Improves user experience
  • Cannot be trusted for security

Server-Side Validation:

  • Critical security control
  • Cannot be bypassed by malicious users
  • Must be implemented even if client-side validation exists

Validation Techniques by Data Type

String Validation

  1. Length constraints:
// Validate string length
function validateLength(input, min, max) {
  return input.length >= min && input.length <= max;
}

// Usage
if (!validateLength(password, 8, 128)) {
  return res.status(400).json({ error: 'Password must be between 8 and 128 characters' });
}
  1. Character set restrictions:
// Validate email format
function validateEmail(email) {
  const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
  return emailRegex.test(email);
}

// Validate alphanumeric with specific special characters
function validatePassword(password) {
  const passwordRegex = /^[a-zA-Z0-9!@#$%^&*()-_=+]+$/;
  return passwordRegex.test(password);
}

Numeric Validation

// Validate integer range
function validateInteger(value, min, max) {
  const num = parseInt(value, 10);
  return !isNaN(num) && Number.isInteger(num) && num >= min && num <= max;
}

// Validate decimal/float
function validateDecimal(value, min, max, precision) {
  const num = parseFloat(value);
  if (isNaN(num) || num < min || num > max) return false;
  
  // Check decimal precision
  const decimalPart = value.toString().split('.')[1] || '';
  return decimalPart.length <= precision;
}

Date Validation

// Validate date format and range
function validateDate(dateString, minDate, maxDate) {
  const date = new Date(dateString);
  
  // Check if date is valid
  if (isNaN(date.getTime())) return false;
  
  // Check range
  if (minDate && date < new Date(minDate)) return false;
  if (maxDate && date > new Date(maxDate)) return false;
  
  return true;
}

File Validation

// Validate file type and size
function validateFile(file, allowedTypes, maxSizeInBytes) {
  // Check file type
  if (!allowedTypes.includes(file.mimetype)) {
    return false;
  }
  
  // Check file size
  if (file.size > maxSizeInBytes) {
    return false;
  }
  
  return true;
}

// Usage
app.post('/upload', upload.single('document'), (req, res) => {
  if (!validateFile(
    req.file, 
    ['application/pdf', 'image/jpeg', 'image/png'], 
    5 * 1024 * 1024  // 5MB
  )) {
    return res.status(400).json({ error: 'Invalid file type or size' });
  }
  
  // Process valid file
});

Framework-Specific Validation

Express.js (Node.js) with express-validator

const { body, validationResult } = require('express-validator');

app.post('/register',
  // Validation middleware
  [
    body('username')
      .isAlphanumeric()
      .isLength({ min: 3, max: 20 })
      .withMessage('Username must be 3-20 alphanumeric characters'),
    
    body('email')
      .isEmail()
      .normalizeEmail()
      .withMessage('Must provide a valid email'),
    
    body('password')
      .isLength({ min: 8 })
      .matches(/[a-z]/).withMessage('Password must include lowercase letter')
      .matches(/[A-Z]/).withMessage('Password must include uppercase letter')
      .matches(/[0-9]/).withMessage('Password must include a number')
      .matches(/[^a-zA-Z0-9]/).withMessage('Password must include a special character')
  ],
  // Handler
  (req, res) => {
    // Check for validation errors
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    
    // Process valid input
    // ...
  }
);

Django (Python)

from django import forms

class UserRegistrationForm(forms.Form):
    username = forms.CharField(
        min_length=3,
        max_length=20,
        validators=[
            RegexValidator(
                regex=r'^[a-zA-Z0-9]+$',
                message='Username must contain only letters and numbers'
            )
        ]
    )
    email = forms.EmailField()
    password = forms.CharField(
        min_length=8,
        widget=forms.PasswordInput,
        validators=[
            RegexValidator(
                regex=r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$',
                message='Password must include uppercase, lowercase, number and special character'
            )
        ]
    )

Sanitization Strategies

Validation should be paired with sanitization to ensure safe use of data.

HTML/XSS Sanitization

// Using DOMPurify library
const DOMPurify = require('dompurify');

function sanitizeHtml(input) {
  return DOMPurify.sanitize(input, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'],
    ALLOWED_ATTR: ['href', 'target']
  });
}

// Using with user input that will be displayed as HTML
const userComment = sanitizeHtml(req.body.comment);

SQL Injection Prevention

// Using parameterized queries with PostgreSQL
const { Pool } = require('pg');
const pool = new Pool();

async function getUser(username) {
  // Safe: Using parameterized query
  const result = await pool.query(
    'SELECT id, name, email FROM users WHERE username = $1',
    [username]
  );
  
  return result.rows[0];
}

Common Validation Vulnerabilities and Mitigations

1. Cross-Site Scripting (XSS)

Vulnerability:

// Unsafe: Directly rendering user input
app.get('/profile', (req, res) => {
  res.send(`<h1>Welcome, ${req.query.name}!</h1>`);
});

Mitigation:

const escapeHtml = require('escape-html');

app.get('/profile', (req, res) => {
  // Escape HTML special characters
  const safeName = escapeHtml(req.query.name || '');
  res.send(`<h1>Welcome, ${safeName}!</h1>`);
});

2. SQL Injection

Vulnerability:

// Unsafe: Constructing SQL with string concatenation
function searchUsers(searchTerm) {
  return db.query(`SELECT * FROM users WHERE username LIKE '%${searchTerm}%'`);
}

Mitigation:

// Safe: Using parameterized queries
function searchUsers(searchTerm) {
  return db.query(
    'SELECT * FROM users WHERE username LIKE $1',
    [`%${searchTerm}%`]
  );
}

3. Mass Assignment / Parameter Pollution

Vulnerability:

// Unsafe: Directly using request body to update records
app.post('/users/:id', (req, res) => {
  const userId = req.params.id;
  db.updateUser(userId, req.body);
});

Mitigation:

// Safe: Explicitly selecting allowed fields
app.post('/users/:id', (req, res) => {
  const userId = req.params.id;
  
  // Only allow specific fields to be updated
  const allowedUpdates = {
    name: req.body.name,
    email: req.body.email,
    bio: req.body.bio
  };
  
  db.updateUser(userId, allowedUpdates);
});

Input Validation Checklist

  • Implement server-side validation for ALL user inputs
  • Use whitelist validation rather than blacklist
  • Validate data type, format, length, and range
  • Sanitize input before use in HTML, SQL, or other contexts
  • Use parameterized queries for database operations
  • Implement context-specific encoding for different output locations
  • Add client-side validation for better user experience
  • Log validation failures for security monitoring
  • Use appropriate HTTP status codes for validation errors (400 Bad Request)
  • Don't reveal too much information in validation error messages

Additional Resources

Clone this wiki locally