Web Application Security: Comprehensive Guide to Protecting Your Applications

25 novembre 2024 · CodeMatic Team

Web Application Security

Web application security is critical in today's threat landscape. This comprehensive guide covers essential security practices, from authentication and encryption to input validation and secure deployment strategies.

Defense in Depth Strategy

Implement multiple layers of security controls. If one layer fails, others provide protection:

  • Network Layer: Firewalls, DDoS protection, WAF
  • Application Layer: Input validation, output encoding, authentication
  • Data Layer: Encryption at rest and in transit
  • Infrastructure Layer: Secure configurations, regular updates

HTTPS and TLS Configuration

Always use HTTPS for all communications. Configure TLS properly:

Strong TLS Configuration

// Nginx TLS configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

# HSTS (HTTP Strict Transport Security)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# Certificate pinning
add_header Public-Key-Pins 'pin-sha256="..."; max-age=2592000' always;

Input Validation and Sanitization

Never trust user input. Validate and sanitize all data:

Server-Side Validation

// Using Zod for validation
import { z } from 'zod';

const userSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1).max(100),
  age: z.number().int().min(18).max(120),
  phone: z.string().regex(/^+?[1-9]\d{1,14}$/),
});

async function createUser(req: Request) {
  const body = await req.json();
  
  // Validate input
  const result = userSchema.safeParse(body);
  if (!result.success) {
    return Response.json(
      { error: 'Validation failed', details: result.error },
      { status: 400 }
    );
  }
  
  // Use validated data
  const user = await db.users.create({ data: result.data });
  return Response.json(user);
}

Output Encoding

// Prevent XSS with output encoding
import DOMPurify from 'isomorphic-dompurify';

// For HTML content
const cleanHTML = DOMPurify.sanitize(userInput);

// For URLs
function sanitizeURL(url: string): string {
  try {
    const parsed = new URL(url);
    // Only allow http/https
    if (!['http:', 'https:'].includes(parsed.protocol)) {
      throw new Error('Invalid protocol');
    }
    return parsed.toString();
  } catch {
    return '#';
  }
}

// For JavaScript contexts
function escapeJS(str: string): string {
  return str
    .replace(/\\/g, '\\\\')
    .replace(/'/g, "\\'")
    .replace(/"/g, '\\"')
    .replace(/\n/g, '\\n')
    .replace(/\r/g, '\\r');
}

SQL Injection Prevention

Always use parameterized queries or ORMs:

// ❌ VULNERABLE - Never do this
const query = `SELECT * FROM users WHERE email = '${email}'`;
db.query(query);

// ✅ SAFE - Use parameterized queries
const query = 'SELECT * FROM users WHERE email = $1';
db.query(query, [email]);

// ✅ SAFE - Use ORM (Prisma)
const user = await prisma.user.findUnique({
  where: { email },
});

// ✅ SAFE - TypeORM
const user = await userRepository.findOne({
  where: { email },
});

Cross-Site Scripting (XSS) Prevention

Content Security Policy (CSP)

// Strict CSP header
Content-Security-Policy: 
  default-src 'self';
  script-src 'self' 'unsafe-inline' https://trusted-cdn.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  connect-src 'self' https://api.example.com;
  font-src 'self';
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';
  upgrade-insecure-requests;

// In Next.js
// next.config.js
const securityHeaders = [
  {
    key: 'Content-Security-Policy',
    value: "default-src 'self'; script-src 'self' 'unsafe-inline'; ..."
  },
];

Cross-Site Request Forgery (CSRF) Protection

// CSRF token implementation
import { randomBytes } from 'crypto';

// Generate CSRF token
function generateCSRFToken(): string {
  return randomBytes(32).toString('hex');
}

// Store in session
req.session.csrfToken = generateCSRFToken();

// Verify in requests
function verifyCSRFToken(req: Request, token: string): boolean {
  return req.session.csrfToken === token;
}

// In forms
<form method="POST">
  <input type="hidden" name="csrf_token" value="{csrfToken}" />
  <!-- form fields -->
</form>

// Verify before processing
if (!verifyCSRFToken(req, req.body.csrf_token)) {
  return Response.json({ error: 'Invalid CSRF token' }, { status: 403 });
}

Secure Headers

// Essential security headers
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=(), camera=()

// Next.js implementation
// next.config.js
const securityHeaders = [
  {
    key: 'X-DNS-Prefetch-Control',
    value: 'on'
  },
  {
    key: 'Strict-Transport-Security',
    value: 'max-age=63072000; includeSubDomains; preload'
  },
  {
    key: 'X-Frame-Options',
    value: 'SAMEORIGIN'
  },
  {
    key: 'X-Content-Type-Options',
    value: 'nosniff'
  },
  {
    key: 'X-XSS-Protection',
    value: '1; mode=block'
  },
  {
    key: 'Referrer-Policy',
    value: 'origin-when-cross-origin'
  },
];

Rate Limiting

// Rate limiting middleware
import rateLimit from 'express-rate-limit';

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
  message: 'Too many requests from this IP',
  standardHeaders: true,
  legacyHeaders: false,
});

// Different limits for different endpoints
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5, // 5 login attempts per 15 minutes
  skipSuccessfulRequests: true,
});

app.use('/api/auth/login', authLimiter);
app.use('/api/', limiter);

Secure Password Storage

import bcrypt from 'bcrypt';

// Hash password
async function hashPassword(password: string): Promise {
  const saltRounds = 12; // Higher is more secure but slower
  return await bcrypt.hash(password, saltRounds);
}

// Verify password
async function verifyPassword(
  password: string,
  hash: string
): Promise {
  return await bcrypt.compare(password, hash);
}

// Password requirements
function validatePassword(password: string): boolean {
  return (
    password.length >= 12 &&
    /[a-z]/.test(password) &&
    /[A-Z]/.test(password) &&
    /[0-9]/.test(password) &&
    /[^a-zA-Z0-9]/.test(password)
  );
}

Secrets Management

  • Never commit secrets to version control
  • Use environment variables for configuration
  • Use secret management services (AWS Secrets Manager, HashiCorp Vault)
  • Rotate secrets regularly
  • Use different secrets for different environments

Dependency Security

// Regularly audit dependencies
npm audit
npm audit fix

// Use Dependabot or Snyk for automated updates
// .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10

// Lock file security
# Use package-lock.json or yarn.lock
# Verify checksums for critical packages

Security Testing

  • Static Analysis: ESLint security plugins, SonarQube
  • Dynamic Analysis: OWASP ZAP, Burp Suite
  • Dependency Scanning: npm audit, Snyk, Dependabot
  • Penetration Testing: Regular security audits
  • Code Reviews: Security-focused reviews

Real-World Security Checklist

  • ✅ HTTPS enforced with HSTS
  • ✅ Strong authentication and session management
  • ✅ Input validation on all user inputs
  • ✅ Output encoding to prevent XSS
  • ✅ Parameterized queries to prevent SQL injection
  • ✅ CSRF protection on state-changing operations
  • ✅ Security headers configured
  • ✅ Rate limiting implemented
  • ✅ Secrets stored securely
  • ✅ Dependencies regularly updated
  • ✅ Security logging and monitoring
  • ✅ Regular security audits

Conclusion

Web application security requires a comprehensive approach. Implement defense in depth with multiple security layers, validate all inputs, use secure coding practices, and maintain security through regular updates and audits. Security is not a one-time task but an ongoing process that requires vigilance and continuous improvement.