Comparing Node.js web frameworks: Which is most secure?

·

7 min read


In this article, we’ll look at three popular frameworks — Express.js, NestJS, and Fastify — and evaluate them according to how well they align with the Node.js security best practices.

JavaScript is the world’s most popular programming language, providing many web frameworks that help developers build secure, reliable Node.js web applications. Each framework has unique features, and which framework is right for you depends on your preference and the type of application you intend to create.

With so many frameworks available, you need a way to assess their security. In this article, we’ll look at three popular frameworks — Express.js, NestJS, and Fastify — and evaluate them according to how well they align with the Node.js security best practices.

Comparing the security of Node.js web frameworks

All JavaScript-based web frameworks are built on top of the Node.js runtime. Applications built around the npm ecosystem typically combine many npm dependencies by adding functionality to your application. As a result, no matter what framework we use, vulnerabilities in Node.js and the npm dependencies are likely to affect the application. Our application’s security depends on adopting best practices and design principles in development.

Web frameworks encourage us to use the most recent packages and to update them regularly. They provide an abstraction layer that reduces the possibility of unauthorized access to the application. For those reasons, we should use a web framework when creating applications.

In the following sections, we’ll discuss and compare the security capabilities of our three Node.js frameworks.

Express.js

Express.js is a fast, minimalist web framework for Node.js designed to build web applications and APIs with the MERN or MEAN stack. This framework encourages developers to adhere to its numerous security best practices before deploying their applications to the production environment. Additionally, Express.js observes Node.js security best practices and offers many recommendations to align with them:

Preventing brute force attacks

Node.js best practice 6.12 deals with preventing brute force attacks against authorization mechanisms. This practice encourages implementing a strategy to limit the number of login attempts on sensitive routes, such as /admin or /login, to mitigate the risk of brute force password and dictionary attacks. We can achieve this using the rate-limiter-flexible npm package to limit login attempts based on the request’s source IP address or request body parameters like the username or email address. Based on these properties, we can create one limiter that counts and restricts the number of consecutive failed attempts. Then, we can add another limiter that blocks an IP address for a specified period when it reaches the limit for consecutive failed attempts. Express.js observes this feature by recommending the use of the same npm package and blocking authorization attempts based on the same metrics.

Ensuring regex security

Node.js best practice 6.16 recommends the use of third-party regex validation packages such as validator.js and safe-regex to detect vulnerable patterns. It discourages implementing your own Regex patterns, as poorly written regexes are susceptible to regex DoS attacks and can be easily exploited to block event loops completely.

Appropriately set HTTP headers

Best practice 6.6 recommends using secure headers to protect applications from vulnerabilities like cross-site scripting, cookie hijacking, and clickjacking. Previously, Express.js encountered a security vulnerability in the forwarded module, which handles the X-Forwarded-For header. To exploit this weakness, a malicious actor could structure a query that, when parsed, blocked a necessary event loop and resulted in a denial of service response. To prevent this and similar vulnerabilities, Express.js incorporates the use of Helmet to easily set our security-related headers and align with Node.js best practices.

NestJS

NestJS is a web application framework that’s heavily inspired by the front-end framework, Angular. NestJS is designed for building scalable back-end applications. As a wrapper for Fastify or Express, NestJS is generally only as secure as those frameworks. The main advantage of Nest is the architectural and structural designs it includes that we can use when developing our application.

NestJS is built on and supports TypeScript, enabling us to create more maintainable and complex applications. It also has Guards, which can determine whether a route handler will handle a specific request.

This framework observes the Node.js security best practices in several ways:

Encryption and hashing

Best practice 6.8 recommends hashing sensitive user data (e.g., passwords) instead of storing them as plain text. This practice lists three functions to use at minimum, but also recommends using higher-level parameters and program-unique combinations to help mitigate the damage incurred if a malicious actor discovers a hash method. Furthermore, this practice recommends the addition of a salt before hashing passwords. NestJS observes this practice by recommending the encryption and hashing of information. For encryption, this framework encourages the use of the built-in crypto module. For hashing, the framework recommends the use of either the bcrypt or argon2 packages.

Validating incoming requests

Node.js best Practice 6.10 recommends validating the incoming request’s body. This means defining what payload the app should accept and failing fast when a different payload is received. This helps to reduce DDOS attacks as an attacker cannot submit custom payloads that contain a different structure, value, and length. NestJS makes it easy to implement this practice by providing several pipes out-of-the-box which can be used to perform validation.

Modifying session middleware settings

Node.js best practice 6.22 recommends tweaking the default cookie settings for increased user and application protection through the reduction of attacks such as session identification and session hijacking. NestJS makes it easy to implement this practice by providing a way to modify session settings. An example of a security vulnerability that affected this framework was in the @finastra/nestjs-proxy module, which is a NestJS Proxy module. The affected versions of this package were vulnerable to information exposure caused by the default forwarding of sensitive cookies.

Fastify

Fastify is a web framework for Node.js that aims to be fast and low overhead. It is inspired by Hapi and Express and is designed to be a simple and stable foundation for building web applications and services. Fastify uses a minimal codebase and provides a simple, declarative configuration that allows developers to get a server up and running quickly.

This framework observes the Node.js security best practices by recommending:

The use of fastify/rate-limit to limit client requests

This aligns with best practice 6.12. According to this practice, rate limiting helps to prevent brute-force attacks against authorization on higher privileged routes such as /login and /admin. Rate limiting can be achieved through creating limiters based on two metrics as discussed here.

The use of fastify/helmet to set important security headers

This aligns with best practice 6.6. Setting appropriate security headers safeguards your application from cross-site scripting, clickjacking, and other attacks.

The use of the community-provided fastify-cockroachdb plugin

This plugin enables developers to connect a CockroachDB PostgreSQL instance via the Sequelize ORM. According to best practice 6.4, ORM/DRM libraries help prevent SQL/NoSQL injections by preventing the use of JavaScript string concatenation and template strings, both of which expose an application to various attack types. This approach also supports using reputable data access libraries like Sequelize, Knex, and mongoose, as they offer built-in protection against injection attacks.

Limiting concurrent requests

Node.js best practice 6.2 encourages the implementation of a rate limiter, which can help prevent too many simultaneous requests — the basis for DoS attacks. This practice encourages using a service designed for this task. Fastify provides the @fastify/rate-limit plugin to help accomplish this.

Removing secrets from config files

Node.js best practice 6.3 recommends storing keys and secrets in encrypted form or using environment variables. Storing them as plain text leaves them susceptible to discovery. Fastify abides to this practice by providing the @fastify/env plugin that allows you to check and load config files. Still, loading secrets from environment variables should be discouraged and a last resort.

One security shortcoming of this framework is that it doesn’t offer support for blocklisting JSON Web Tokens (JWTs). Blocklisting JWTs helps to ensure an attacker can’t use expired or misplaced tokens to impersonate the owner of the token.

A recent example of a security vulnerability in this framework is the DoS attack that affected some versions of the framework.

Choosing the right framework

All the web frameworks discussed in this article adhere to security best practices to an acceptable level. However, NestJS adheres to Node.js security best practices most closely, as it provides an abstraction layer over Express.js with built-in security features, making it more difficult for attackers to access the system.

The frameworks we have looked at each have their strengths and ideal use cases. Each framework also supports additional packages you can integrate to create secure and robust applications.

For example, Express.js is the most popular and well-established framework, with excellent documentation and widespread community support.

NestJS is a good choice for scalable applications because it has many built-in packages, provides an abstraction over Express.js, and supports TypeScript. Finally, Fastify focuses on fast and efficient web apps without sacrificing security.

Regardless of which framework you’re using, as a developer, you must select the appropriate set of dependencies and adhere to security best practices when creating a web application. It’s also crucial to update the most recent packages to eliminate known vulnerabilities.