Web application security is paramount in today's digital landscape. This comprehensive guide covers essential security practices every developer must implement to protect users and data.
Authentication and Authorization
Secure Password Handling
Never store passwords in plain text. Use proper hashing:
const bcrypt = require('bcrypt');
async function hashPassword(password) {
const saltRounds = 12;
return await bcrypt.hash(password, saltRounds);
}
async function verifyPassword(password, hash) {
return await bcrypt.compare(password, hash);
}
JWT Best Practices
const jwt = require('jsonwebtoken');
// Generate token with expiration
const token = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
// Verify token middleware
const verifyToken = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(403).json({ error: 'Invalid token' });
}
};
Input Validation and Sanitization
Server-Side Validation
const joi = require('joi');
const userSchema = joi.object({
email: joi.string().email().required(),
password: joi.string().min(8).pattern(new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\\$%\\^&\\*])')).required(),
name: joi.string().min(2).max(50).required()
});
app.post('/register', async (req, res) => {
const { error, value } = userSchema.validate(req.body);
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
// Process validated data
});
Cross-Site Scripting (XSS) Prevention
Content Security Policy
// Express.js with helmet
const helmet = require('helmet');
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'", "https://apis.google.com"],
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
imgSrc: ["'self'", "data:", "https:"]
}
}));
Input Sanitization
const DOMPurify = require('isomorphic-dompurify');
function sanitizeHtml(dirty) {
return DOMPurify.sanitize(dirty, {
ALLOWED_TAGS: ['p', 'br', 'strong', 'em'],
ALLOWED_ATTR: []
});
}
SQL Injection Prevention
Always use parameterized queries:
// ❌ Vulnerable
const query = SELECT * FROM users WHERE email = '${email}'
;
// ✅ Secure
const query = 'SELECT * FROM users WHERE email = ?';
const user = db.prepare(query).get(email);
HTTPS and Security Headers
// Force HTTPS
app.use((req, res, next) => {
if (req.header('x-forwarded-proto') !== 'https') {
res.redirect(https://${req.header('host')}${req.url}
);
} else {
next();
}
});
// Security headers
app.use(helmet({
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}));
Rate Limiting
const rateLimit = require('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'
});
app.use('/api/', limiter);
Security is an ongoing process. Regular security audits, dependency updates, and staying informed about new vulnerabilities are essential for maintaining secure applications.