Understanding DDoS Attacks and Protecting Your Express.js Application

What is a DDoS Attack?

A Distributed Denial of Service (DDoS) attack is a malicious attempt to disrupt the normal functioning of a targeted server, service, or network by overwhelming it with a flood of internet traffic. Unlike a regular Denial of Service (DoS) attack that comes from a single source, DDoS attacks utilize multiple compromised devices (often forming a botnet) to generate massive traffic volumes.

Common types of DDoS attacks include:

  1. Volumetric Attacks: Overwhelm bandwidth with massive data floods

  2. Protocol Attacks: Exploit weaknesses in network protocols

  3. Application Layer Attacks: Target specific application vulnerabilities

These attacks can cause:

  • Website downtime

  • Slow performance

  • Loss of revenue

  • Damaged reputation

Handling DDoS Attacks in Express.js

Express.js is a popular Node.js framework for building web applications. While no system can be completely immune to DDoS attacks, you can implement several strategies to mitigate and handle them effectively.

1. Rate Limiting

Implement rate limiting to restrict the number of requests a client can make in a given timeframe:

const express = require('express');
const rateLimit = require('express-rate-limit');
const app = express();

// Apply rate limiting to all requests
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, please try again later.'
});

app.use(limiter);

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

2. Use Middleware for Request Validation

Add middleware to filter suspicious requests:

const express = require('express');
const app = express();

// Basic request validation middleware
const validateRequest = (req, res, next) => {
  // Check for suspicious user agents
  const userAgent = req.get('User-Agent');
  if (!userAgent || userAgent.includes('bot')) {
    return res.status(403).send('Access denied');
  }

  // Validate request size
  if (req.headers['content-length'] > 10000) {
    return res.status(413).send('Request too large');
  }

  next();
};

app.use(validateRequest);

3. Implement Request Timeout

Set timeouts to prevent slow requests from clogging your server:

const express = require('express');
const app = express();

app.use((req, res, next) => {
  req.setTimeout(10000, () => {
    res.status(408).send('Request timeout');
  });
  next();
});

4. Use a Reverse Proxy (e.g., Nginx)

Configure Nginx as a reverse proxy with additional DDoS protection:

http {
  limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;

  server {
    location / {
      limit_req zone=mylimit burst=20 nodelay;
      proxy_pass http://localhost:3000;
    }
  }
}

5. Implement Caching

Use caching to reduce server load:

const express = require('express');
const apicache = require('apicache');
const app = express();

const cache = apicache.middleware;

app.use(cache('5 minutes'));

app.get('/api/data', (req, res) => {
  // Your API logic here
  res.json({ data: 'Some data' });
});

6. Additional Security Measures

const express = require('express');
const helmet = require('helmet');
const app = express();

// Add security headers
app.use(helmet());

// Body parser with limits
app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ limit: '10kb', extended: true }));

// Error handling for too many requests
app.use((err, req, res, next) => {
  if (err.status === 429) {
    return res.status(429).send('Too Many Requests');
  }
  next(err);
});

Best Practices for DDoS Protection

  1. Monitoring:
  • Set up real-time monitoring

  • Track traffic patterns

  • Set alerts for unusual activity

  1. Infrastructure:
  • Use a Content Delivery Network (CDN) like Cloudflare

  • Implement load balancing

  • Scale servers dynamically

  1. Preparedness:
  • Create an incident response plan

  • Maintain backups

  • Test your defenses regularly

Complete Example

const express = require('express');
const rateLimit = require('express-rate-limit');
const helmet = require('helmet');
const apicache = require('apicache');

const app = express();

// Security headers
app.use(helmet());

// Rate limiting
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100
});
app.use(limiter);

// Caching
app.use(apicache.middleware('5 minutes'));

// Request validation
app.use((req, res, next) => {
  req.setTimeout(10000);
  if (!req.get('User-Agent')) {
    return res.status(403).send('Access denied');
  }
  next();
});

// Routes
app.get('/', (req, res) => {
  res.send('Hello World');
});

// Error handling
app.use((err, req, res, next) => {
  if (err.status === 429) {
    return res.status(429).send('Too Many Requests');
  }
  res.status(500).send('Server Error');
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Conclusion

While Express.js alone can't fully protect against large-scale DDoS attacks, combining these techniques with proper infrastructure setup can significantly improve your application's resilience. For comprehensive protection, consider:

  • Using cloud-based DDoS protection services

  • Implementing Web Application Firewalls (WAF)

  • Maintaining a scalable infrastructure

  • Regular security audits

4
Subscribe to my newsletter

Read articles from Antarip Chatterjee directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Antarip Chatterjee
Antarip Chatterjee