Securing Node.js Applications
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:
Morgan: It can be used for logging HTTP requests in the console.
app.use(morgan('dev'))
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
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🎊!!
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