Back to Blog
Security
October 5, 2025
13 min read
Edison Nkemande

Security Best Practices for Web Applications

Protect your applications against common vulnerabilities including XSS, CSRF, SQL injection, and authentication attacks.

Introduction

Security is not optional. This comprehensive guide covers essential security practices for web applications.

Common Vulnerabilities (OWASP Top 10)

1. Broken Authentication

Implement strong authentication:

// Use bcrypt for password hashing
import bcrypt from 'bcrypt';
 
// Hashing passwords
const hashedPassword = await bcrypt.hash(password, 10);
 
// Validating passwords
const isValid = await bcrypt.compare(inputPassword, hashedPassword);
 
// Never return sensitive data
const user = await db.users.findById(id);
delete user.password; // Remove before sending
return user;

2. SQL Injection

Use parameterized queries:

// ✗ Bad: SQL injection vulnerability
const query = `SELECT * FROM users WHERE email = '${email}'`;
db.query(query);
 
// ✓ Good: Parameterized queries
const user = await db.users.findUnique({
  where: { email }
});
 
// Or with raw SQL
const query = 'SELECT * FROM users WHERE email = $1';
db.query(query, [email]);

3. Cross-Site Scripting (XSS)

Sanitize user input:

// ✗ Bad: Vulnerable to XSS
function Comment({ text }) {
  return <div dangerouslySetInnerHTML={{ __html: text }} />;
}
 
// ✓ Good: Automatically escaped
function Comment({ text }) {
  return <div>{text}</div>;
}
 
// If HTML needed, use a sanitizer
import DOMPurify from 'dompurify';
 
function SafeHTML({ html }) {
  const cleanHTML = DOMPurify.sanitize(html);
  return <div dangerouslySetInnerHTML={{ __html: cleanHTML }} />;
}

4. Cross-Site Request Forgery (CSRF)

Implement CSRF tokens:

// Middleware to check CSRF token
app.post('/api/users', csrfProtection, (req, res) => {
  // Token validated by middleware
  res.json({ success: true });
});
 
// In form
<form method="POST" action="/api/users">
  <input type="hidden" name="_csrf" value={csrfToken} />
  <button type="submit">Submit</button>
</form>

5. Sensitive Data Exposure

Protect sensitive data:

// Use HTTPS only
const helmet = require('helmet');
app.use(helmet());
 
// Don't log sensitive data
logger.info('User created'); // ✓
logger.info(`Password: ${password}`); // ✗
 
// Encrypt sensitive data
const crypto = require('crypto');
const encrypted = crypto.encrypt(sensitiveData);
 
// Use environment variables for secrets
const apiKey = process.env.API_KEY;

Authentication & Authorization

JWT Implementation

import jwt from 'jsonwebtoken';
 
// Generate token
const token = jwt.sign(
  { userId: user.id, email: user.email },
  process.env.JWT_SECRET,
  { expiresIn: '24h' }
);
 
// Verify token
function verifyToken(token) {
  try {
    return jwt.verify(token, process.env.JWT_SECRET);
  } catch (error) {
    return null;
  }
}
 
// Middleware
function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];
 
  if (!token) return res.sendStatus(401);
 
  const user = verifyToken(token);
  if (!user) return res.sendStatus(403);
 
  req.user = user;
  next();
}

Security Headers

app.use(helmet()); // Sets multiple security headers
 
// Or manually:
app.use((req, res, next) => {
  res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('Content-Security-Policy', "default-src 'self'");
  next();
});

Rate Limiting

Prevent brute force attacks:

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
});
 
app.post('/api/login', limiter, (req, res) => {
  // Login logic
});

Input Validation

import { z } from 'zod';
 
const userSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
  age: z.number().int().positive()
});
 
function createUser(data) {
  const validated = userSchema.parse(data);
  // Use validated data
}

Dependency Management

# Check for vulnerabilities
npm audit
 
# Fix automatically
npm audit fix
 
# Use security scanning tools
npm install --save-dev snyk
npx snyk test

Security Checklist

  • [ ] HTTPS enabled
  • [ ] Strong password hashing (bcrypt, argon2)
  • [ ] Rate limiting implemented
  • [ ] CSRF protection enabled
  • [ ] Input validation on all endpoints
  • [ ] Output encoding/escaping
  • [ ] Security headers set
  • [ ] Dependency vulnerabilities checked
  • [ ] Secrets not in version control
  • [ ] Logging doesn't expose sensitive data

Conclusion

Security requires continuous attention. Follow these practices and regularly audit your applications to maintain strong security posture.

Share this article:

Related Articles