Web Security Best Practices: Protecting Modern Applications
Security is a critical aspect of web development that requires constant attention and updates. Let's explore essential security practices and implementations to protect modern web applications.
Authentication Implementation
1. Secure Password Handling
Implement secure password hashing and validation:
// auth/password.service.ts import * as bcrypt from "bcrypt"; import * as zxcvbn from "zxcvbn"; export class PasswordService { private static readonly SALT_ROUNDS = 12; private static readonly MIN_PASSWORD_STRENGTH = 3; static async hashPassword(password: string): Promise<string> { return bcrypt.hash(password, this.SALT_ROUNDS); } static async validatePassword( password: string, hashedPassword: string ): Promise<boolean> { return bcrypt.compare(password, hashedPassword); } static validatePasswordStrength(password: string): { isStrong: boolean; feedback: string[]; } { const result = zxcvbn(password); return { isStrong: result.score >= this.MIN_PASSWORD_STRENGTH, feedback: result.feedback.suggestions, }; } } // auth/password.middleware.ts export function validatePasswordStrength( req: Request, res: Response, next: NextFunction ) { const { password } = req.body; const strength = PasswordService.validatePasswordStrength(password); if (!strength.isStrong) { return res.status(400).json({ error: "Password too weak", feedback: strength.feedback, }); } next(); }
2. JWT Authentication
Implement secure JWT handling:
// auth/jwt.service.ts import { sign, verify } from "jsonwebtoken"; export class JWTService { private static readonly JWT_SECRET = process.env.JWT_SECRET!; private static readonly JWT_EXPIRES_IN = "1h"; static generateToken(payload: any): string { return sign(payload, this.JWT_SECRET, { expiresIn: this.JWT_EXPIRES_IN, algorithm: "HS256", }); } static verifyToken(token: string): any { try { return verify(token, this.JWT_SECRET); } catch (error) { throw new UnauthorizedError("Invalid token"); } } static refreshToken(oldToken: string): string { const payload = this.verifyToken(oldToken); delete payload.exp; delete payload.iat; return this.generateToken(payload); } } // auth/jwt.middleware.ts export function authenticateJWT( req: Request, res: Response, next: NextFunction ) { const authHeader = req.headers.authorization; if (!authHeader?.startsWith("Bearer ")) { throw new UnauthorizedError("No token provided"); } const token = authHeader.split(" ")[1]; try { const payload = JWTService.verifyToken(token); req.user = payload; next(); } catch (error) { throw new UnauthorizedError("Invalid token"); } }
XSS Prevention
1. Content Security Policy
Implement CSP headers:
// security/csp.middleware.ts export function setupCSP(req: Request, res: Response, next: NextFunction) { res.setHeader( "Content-Security-Policy", [ "default-src 'self'", "script-src 'self' 'unsafe-inline' 'unsafe-eval'", "style-src 'self' 'unsafe-inline'", "img-src 'self' data: https:", "font-src 'self'", "connect-src 'self' https://api.example.com", "frame-ancestors 'none'", "base-uri 'self'", "form-action 'self'", ].join("; ") ); next(); } // app.ts app.use(setupCSP);
2. Input Sanitization
Implement input sanitization:
// security/sanitization.service.ts import { sanitize } from "isomorphic-dompurify"; import { escape } from "html-escaper"; export class SanitizationService { static sanitizeHTML(input: string): string { return sanitize(input, { ALLOWED_TAGS: ["b", "i", "em", "strong", "a"], ALLOWED_ATTR: ["href"], }); } static escapeHTML(input: string): string { return escape(input); } } // security/sanitization.middleware.ts export function sanitizeInputs( req: Request, res: Response, next: NextFunction ) { if (req.body) { Object.keys(req.body).forEach((key) => { if (typeof req.body[key] === "string") { req.body[key] = SanitizationService.escapeHTML(req.body[key]); } }); } next(); }
CSRF Protection
1. CSRF Token Implementation
Implement CSRF protection:
// security/csrf.service.ts import { randomBytes } from "crypto"; export class CSRFService { private static readonly TOKEN_LENGTH = 32; static generateToken(): string { return randomBytes(this.TOKEN_LENGTH).toString("hex"); } static validateToken(token: string, storedToken: string): boolean { return token === storedToken; } } // security/csrf.middleware.ts export function csrfProtection( req: Request, res: Response, next: NextFunction ) { if (req.method === "GET") { const token = CSRFService.generateToken(); res.cookie("XSRF-TOKEN", token, { httpOnly: false, secure: process.env.NODE_ENV === "production", sameSite: "strict", }); next(); } else { const token = req.headers["x-xsrf-token"]; const storedToken = req.cookies["XSRF-TOKEN"]; if ( !token || !storedToken || !CSRFService.validateToken(token, storedToken) ) { throw new ForbiddenError("Invalid CSRF token"); } next(); } }
Rate Limiting
1. Rate Limiter Implementation
Implement rate limiting:
// security/rate-limit.service.ts import { RateLimiterRedis } from "rate-limiter-flexible"; import Redis from "ioredis"; export class RateLimitService { private static redis = new Redis({ host: process.env.REDIS_HOST, port: parseInt(process.env.REDIS_PORT!), password: process.env.REDIS_PASSWORD, }); private static limiter = new RateLimiterRedis({ storeClient: this.redis, keyPrefix: "rate_limit", points: 100, // Number of points duration: 60, // Per 60 seconds }); static async checkRateLimit(ip: string): Promise<void> { try { await this.limiter.consume(ip); } catch (error) { throw new TooManyRequestsError("Rate limit exceeded"); } } } // security/rate-limit.middleware.ts export async function rateLimiter( req: Request, res: Response, next: NextFunction ) { try { await RateLimitService.checkRateLimit(req.ip); next(); } catch (error) { if (error instanceof TooManyRequestsError) { res.status(429).json({ error: "Too many requests", message: "Please try again later", }); } else { next(error); } } }
Security Headers
1. Security Headers Implementation
Set secure HTTP headers:
// security/headers.middleware.ts import helmet from "helmet"; export const securityHeaders = helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'"], styleSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", "data:", "https:"], connectSrc: ["'self'"], fontSrc: ["'self'"], objectSrc: ["'none'"], mediaSrc: ["'self'"], frameSrc: ["'none'"], }, }, crossOriginEmbedderPolicy: true, crossOriginOpenerPolicy: true, crossOriginResourcePolicy: { policy: "same-origin" }, dnsPrefetchControl: true, expectCt: { maxAge: 86400, enforce: true, }, frameguard: { action: "deny" }, hidePoweredBy: true, hsts: { maxAge: 31536000, includeSubDomains: true, preload: true, }, ieNoOpen: true, noSniff: true, originAgentCluster: true, permittedCrossDomainPolicies: { permittedPolicies: "none" }, referrerPolicy: { policy: "strict-origin-when-cross-origin" }, xssFilter: true, });
Best Practices
- Keep Dependencies Updated: Regularly update and audit dependencies
- Implement Security Headers: Use security headers to protect against common attacks
- Use HTTPS: Always use HTTPS in production
- Input Validation: Validate and sanitize all user inputs
- Secure Session Management: Implement secure session handling
- Error Handling: Implement proper error handling without exposing sensitive information
- Regular Security Audits: Conduct regular security audits and penetration testing
- Logging and Monitoring: Implement comprehensive security logging and monitoring
Implementation Checklist
- Set up authentication system
- Implement password policies
- Configure security headers
- Set up CSRF protection
- Implement rate limiting
- Configure input sanitization
- Set up logging and monitoring
- Conduct security testing
Conclusion
Web security is an ongoing process that requires constant attention and updates. Implement these security measures and regularly review and update your security practices to protect your applications effectively.