Top 10 Node.js Security Best Practices
Written by Sagiv Michael on
Top 10 Node.js Security Best Practices
Written by Sagiv Michael on
Introduction
Node.js is an open-source, cross-platform JavaScript runtime environment built on Chrome’s V8 JavaScript engine. It allows developers to execute JavaScript code outside a web browser, making it an excellent choice for server-side and network applications. With its event-driven, non-blocking I/O model, Node.js enables efficient handling of concurrent connections and scalability, making it highly suitable for building real-time and data-intensive applications.
Express.js, commonly known as Express, is a popular and minimalist web application framework for Node.js. It provides robust features and tools to build web applications and APIs quickly and efficiently. Express.js is widely used in the Node.js community due to its simplicity, flexibility, and ease of use. It acts as a middleware to handle requests and responses, making it a fundamental part of many Node.js applications.
This article will discuss ten of the best security practices for Node.js and Express.js and how to apply them in your web application. These security practices are organized in descending order from highest to lowest risks.
1. Mitigating Cross-Site Scripting (XSS) Attacks
Cross-site scripting (XSS) is a security vulnerability that allows attackers to inject arbitrary JavaScript code into web pages viewed by other users and perform malicious actions. To prevent XSS attacks, it is essential to properly encode user-generated content before displaying it in HTML templates.
For this purpose, the Pug package can be used. Pug, formerly known as Jade, is a high-performance and feature-rich template engine for Node.js. It allows you to write reusable HTML and includes powerful features for layout and templating.
One of the key features of Pug, as it relates to security, is that it automatically encodes output, which helps to mitigate Cross-Site Scripting (XSS) attacks.
To install the “pug” package, execute the following command:
npm install pug
2. Mitigating NoSQL Injection Attacks
Unlike SQL injection, NoSQL injection attacks target NoSQL databases used in web applications. To protect against NoSQL injection, utilize parameterized queries or Object Data Modeling (ODM) libraries that automatically handle escaping and sanitization.
Mongoose is a MongoDB object modeling tool designed to work in an asynchronous environment. It provides a straightforward, schema-based solution to model the application data, including built-in type casting, validation, query building, and business logic hooks. In other words, Mongoose is an Object Data Modeling (ODM) library for MongoDB and Node.js. It manages relationships between data, provides schema validation, and is used to translate between objects in code and the representation of those objects in MongoDB.
To install the “mongoose” package, execute the following command:
npm install mongoose
3. Mitigating Cross-Site Request Forgery (CSRF) Attacks
CSRF attacks trick authenticated users into unknowingly executing malicious actions on a web application.
A common approach to mitigate CSRF attacks involves using a CSRF token, a random value generated for each form or session, which is then validated with each request. The server only processes the request if the CSRF token is valid. Because the CSRF token is specific to each user and each session, an attacker can’t simply guess the value of the CSRF token, which makes CSRF attacks much more difficult.
In Node.js, the “csrf” middleware package provides easy-to-use protection against CSRF attacks.
To install the “csrf” package, execute the following command:
npm install csrf
4. Validating User Input
Server-side development must always involve strict user input validation to prevent potential issues, such as SQLi and XSS. One way to achieve this is by using the “Ajv” validator, which is a fast JSON Schema validator for Node.js.
Example code using “Ajv” validator:
In this example, the schema defines a JSON schema that requires an object with a name (a string) and age (an integer). The name field is required, but the age field is optional. The validate function is a compiled function from the schema that can be used to validate data. If the data is invalid, validate(data) will return false, and validate.errors will contain an array of error objects describing the validation errors.
A JSON schema validator like “Ajv” can add an extra layer of security to the application by ensuring incoming data conforms to a specified format before processing occurs. This can help protect against several potential security vulnerabilities:
- Injection Attacks: By validating input data against a strict schema, you can reduce the risk of injection attacks such as SQL injection, where an attacker tries to sneak malicious input into your application that changes the behavior of an SQL query or similar attacks on other data processing systems. If the input doesn’t match the expected format, it can be rejected before it reaches your query or command.
- Data Integrity: Ajv can ensure that the data your application processes meet specific criteria, such as string length, numeric range, or matching a particular pattern. This can help prevent attacks where an attacker submits data that is technically the correct type but is otherwise malformed or problematic.
- Logic Attacks: By enforcing required properties, you can avoid situations where missing data might cause your application logic to behave unexpectedly, potentially opening up security holes, such as Denial-of-Service (DoS) attacks. By limiting the size and complexity of accepted JSON data, you can protect against DoS attacks where an attacker tries to overload your system with excessively large or complex input.
To install the “ajv” package, execute the following command:
npm install ajv
5. Controlling User Access with JWT
JSON Web Tokens (JWT) are widely used to transfer authentication data in client-server applications. When using JWT, it is essential to control user access based on specific permissions or roles. For this purpose, you can leverage the “express-jwt-permissions” package along with “express-jwt”, which is the most used middleware package in Node.js for deploying and validating JWTs.”
To install the “express-jwt-permissions” package, execute the following command:
npm install express-jwt-permissions
6. Protecting Against Brute Force Attacks
The term “brute force” refers to attempting every possible combination until one works. It essentially uses “brute” computational power to force unauthorized access to a system.
For example, if an attacker is trying to crack a four-digit PIN, the attacker might start with “0000”, then try “0001”, “0002”, and so on, all the way up to “9999”.
Brute force attacks can be time-consuming and resource-intensive. The more complex the password or encryption key (i.e., the longer it is, the more types of characters it includes), the more potential combinations there are to try, and the longer a brute force attack will take.
To mitigate this risk is required to implement a rate-limiting protection mechanism using the “rate-limiter-flexible” package. This package works well with any Node.js framework and allows control of the number of requests sent per time frame from a particular IP address.
To install “rate-limiter-flexible,” execute the following command:
npm install rate-limiter-flexible
Alternatively, the “express-rate-limit” package can be used for simpler rate-limiting implementations. This package is more straightforward because it has fewer configuration options and no need to explicitly consume points or handle rejections as with rate-limiter-flexible.
To install “express-rate-limit” execute the following command:
npm install express-rate-limit
7. Securing Your Cookies
In Express.js version 4, cookies can be managed using two built-in modules in Express.js: “express-session” and “cookie-session.”
The “cookie-session” stores session data directly in cookies, unlike “express-session” which stores the session ID in the cookie and the session data on the server.
For efficient cookie handling, “cookie-session” is generally preferred because it stores the session data directly in a cookie sent to the client. When the client makes subsequent requests, the session data is sent directly to the server in the cookie, eliminating the need for a round trip to a session store to retrieve the session data from the server. However, if complex session data should be stored (cookie-session is limited to 4KB) or ensure that the data remains hidden from the client, “express-session” is a better choice.
When setting cookie security attributes, using the following properties is a best practice:
- Secure – When the attribute is set, the cookie will only be transmitted over secure (HTTPS) connections. This helps to prevent the cookie from being eavesdropped on by unauthorized parties. It’s a crucial setting for any data that should be kept confidential.
- httpOnly – The httpOnly attribute ensures that the cookie is inaccessible to client-side scripts, such as JavaScript. This reduces the risk of cross-site scripting (XSS) attacks, where an attacker might attempt to read sensitive cookie data through client-side scripting.
- Domain – The domain attribute specifies which hosts are allowed to receive the cookie. If not set, the cookie will only be sent to the origin server. If set, the cookie can be accessed by the specified domain and its subdomains, so care should be taken not to set this too broadly.
- Path – The path attribute sets the URL path that must exist in the requested URL for the cookie to be sent. This allows for more granular control over where the cookie is sent and can be used to restrict the cookie to specific parts of the site.
- expires – The expires attribute defines a specific date and time when the cookie should expire and be deleted from the client’s browser. If this attribute is not set, the cookie will expire at the end of the session (i.e., when the browser is closed). By setting an expiration date, you can control how long the cookie persists, minimizing risks associated with stale or unnecessary cookies.
8. Ensuring Secure Connections and Data Communication Transfer
Securing your application’s connections and data communication transfer is essential. To achieve this, you can rely on Helmet.js from the Node.js modules, which provides 13 middleware functions to set necessary HTTP response headers. These headers include Content Security Policy, handling Certificate Transparency, preventing clickjacking, turning off client-side caching, and mitigating XSS attacks.
To begin with, set the “HSTS (HTTP-Strict-Transport-Security)” header. HSTS is a web security policy mechanism that helps to protect websites against protocol downgrade attacks and cookie hijacking. It allows web servers to declare that web browsers (or other complying user agents) should only interact with it using secure HTTPS connections and never allow communication via the insecure HTTP protocol.
To install the Helmet.js package, execute the following command:
npm install hpp
9. Ensuring Secure Software Dependencies
When working with npm (node package manager) for web development, using the latest secure version of the software dependencies is vital to avoid potential vulnerabilities that may compromise the application. Regularly perform dependency checks using a dedicated command to identify and fix security issues in your project’s dependencies.
To analyze the tree of dependencies, you can execute the following command:
npm audit
10. Using an Updated Version of Express.js
When developing an Express.js application, it is crucial to use up-to-date and supported versions. Versions 2 and 3 of Express are deprecated and no longer receive security or performance updates.
To ensure the best development experience, switch to Express.js version 4. This version (according to the time of writing this article) brings a significant evolution, introducing improvements in the routing system, middleware handling, and other essential aspects.
To achieve automatic updates for all Node.js packages in your system, you can set up a weekly cron job on Linux or a scheduled task on Windows. For instance, to schedule a weekly task in Linux, you can follow the example below:
An example of scheduling a task every week using Linux:
0 0 * * 0 npm update -g
This cron expression breaks down as follows:
0: Minute (0-59)
0: Hour (0-23)
*: Day of the month (1-31)
*: Month (1-12)
0: Day of the week (0-7, where both 0 and 7 represent Sunday)
Conclusion
Ensure a secure web application with Node.js and Express by using the latest version, securing connections and data, safeguarding cookies, updating dependencies, validating user input, defending against brute force attacks, and controlling user access with JWT.
Organizations should prioritize cyber security risk assessments and penetration tests to mitigate risks in developing Node.js applications, which have become increasingly popular among companies developing SaaS products. Clear Gate, a trusted cybersecurity provider, offers in-depth manual penetration tests to help organizations strengthen their web application security and protect valuable data from potential threats.