Securing Node.js Applications

Sahib SinghSahib Singh
3 min read

As developers, ensuring the security of our applications is paramount. In the world of backend development with Node.js, several key practices and tools can significantly enhance the security of your application. Let's dive into a comprehensive guide covering the essential backend security best practices.

1. Secure Dependencies

The foundation of any secure application starts with the packages it depends on. Regularly audit and fix vulnerabilities using the following commands for Yarn and NPM:

For Yarn:

yarn audit
yarn audit fix

For NPM:

npm audit
npm audit fix

2. CORS Implementation

Cross-Origin Resource Sharing (CORS) is vital for controlling which origins can access your Node.js application's resources. Implement CORS using the following code snippet:

const whitelist = ["http://localhost:3000"];
const corsOptions = {
  origin: function (origin, callback) {
    if (whitelist.indexOf(origin) !== -1) {
      callback(null, true);
    } else {
      callback(new Error("Not allowed by CORS"));
    }
  },
};

app.use(cors(corsOptions));

3. Helmet.js for HTTP Headers

Helmet.js is an Express middleware that helps set various HTTP headers for enhanced security. Integrate it into your application with the following line:

app.use(helmet());

4. Rate Limiting

Protect your application from abuse and excessive requests with rate limiting. Define a limiter middleware, allowing exceptions for specific IPs:

const allowlist = ["192.168.0.56", "192.168.0.21"]
const limiter = rateLimit({
  windowMs: 60 * 1000, // 1 minute
  max: 50, // 50 requests per IP
  message: "Too many requests from this IP, please try again after 1 minute!",
  skip: (req, res) => allowlist.includes(req.ip),
});

app.use(limiter);

5. Request Size Limit

Implement a request size limit to prevent excessively large payloads that could lead to server issues or DoS attacks:

app.use(express.json({ limit: '10kb' }));

6. HTTP Parameter Pollution Prevention

It is an attack in which attackers send multiple HTTP parameters with the same name and this causes your application to interpret them unpredictably. When multiple parameter values are sent, Express populates them in an array.

// Not Preferred
import hpp from 'hpp'

app.use(hpp())

This import statement is very costly, as it imports a large package.

Solving this problem by creating a custom middleware:

// Preferred
const hppMiddleware = (req, res, next) => {
  if (req.query && typeof req.query === "object") {
    for (const key in req.query) {
      if (Array.isArray(req.query[key])) {
        req.query[key] = req.query[key][req.query[key].length - 1];
      }
    }
  }
  next();
};

app.use(hppMiddleware);

7. Logging for Visibility

Logging is crucial for understanding your application's behavior and diagnosing issues. Use Morgan for logging HTTP requests in the console, and Winston for more versatile logging, including file storage:

  1. Morgan: It can be used for logging HTTP requests in the console.

    app.use(morgan('dev'))

  2. Winston: It can be used for logging all types of requests in a separate file or in the console or in both.

     import { createLogger, transports, format } from "winston";
    
     const logger = createLogger({
       level: "verbose",
       format: format.combine(format.timestamp(), format.json()),
       transports: [
         // new transports.Console({
         //     format: format.combine(
         //         format.colorize(),
         //         format.simple()
         //     ),
         // }),
         new transports.File({ filename: "logs/error.log", level: "error" }),
         new transports.File({ filename: "logs/combined.log" }),
       ],
     });
    

    For using logger in each request

    logger.info("Request received: ${req.method} ${req.url}")

    For using logger as middleware

     const loggerMiddleware = (req, res, next) => {
       logger.info(
         `${req.method} ${req.protocol}: ${req.get("host")}${req.originalUrl}`
       );
       next();
     };
    
     app.use(loggerMiddleware);
    

8. Additional Considerations

We ignore them sometimes

  • Perform Input Validation

  • Only Return What Is Necessary

  • Remove Unnecessary Routes

  • Implement Captcha

Some Useful Resources

Nodejs Security - OWASP Cheat Sheet Series

Node.js Security Best Practices | Node.js

Top 10 Node.js Security Best Practices - Risks & Prevention | Snyk

Guide to Security in Node.js

Conclusion

By incorporating these backend security best practices, you fortify your Node.js application against common vulnerabilities. Regularly update dependencies, control resource access, set HTTP headers, and employ logging to maintain a robust and secure backend. Happy Coding🎊!!

0
Subscribe to my newsletter

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

Written by

Sahib Singh
Sahib Singh

NITian | Full Stack Web Developer | DevOps Enthusiast