2.Motherboard

Due to its ability to act as the backend server for web applications, Node.js is becoming a trendy platform these days. However, it becomes crucial to take into account Node.js security policies when it comes to the world of microservices.

 

Open-source backend frameworks have long had security flaws, and every Node.js developer knows the dangers that hackers pose to apps and user data.  

The article will concentrate on the risks and solutions developers may use to increase the security of their online applications while keeping Node.js security issues in mind.

Why Is Node.js Popular in Back-End Development?

JavaScript has strong frameworks and libraries and has been around for a while. Still, it has never had any backend platforms that could contend with other programming languages. Node.js can address this problem.  Nodejs

Some of the advantages of using Node.js are listed below.

  • Easy to learn: JavaScript proficiency is prevalent among many developers. Node.js uses JavaScript. As a result, picking up Node.js is not difficult and can be learned in a few weeks.
  • Scalability: Scalable network applications are the focus of the design of Node.js.This is why it gained popularity among developers and big businesses so quickly.
  • Flexibility: Node.js gets big thumbs up for allowing developers to create adaptable programs that function flawlessly on any platform. Node.js helped alleviate the concern that apps wouldn't work on various operating systems. 
  • Light and fast: Node.js uses a high-performance, open-source JavaScript and WebAssembly engine. In a single asynchronous thread, it responds to requests. This lessens both the CPU and memory load. Your app will become lighter as a result.

The possibility that hackers will attempt to find vulnerabilities increases with the framework's popularity. As a result, Node.js security should always be taken care of.

Potential Risks for Node.JS Applications

Not all security concerns are serious, and here are the primary security considerations on Node.js.

Code injection

The primary duty of an application developer is to write secure code. However, you cannot ultimately ensure the security of your codebase while using open-source software. 

Any malware in which the hacker inserts code into the system and forces the application function to run is called a code injection attack. The attacker looks at the sloppy and uncertain data to learn more about your codebase.

A common code injection attack that most people run into while developing software is SQL injection. Here, the hackers use malicious SQL code to change the backend database to get sensitive data that is not usually visible.  

Cross-site request forgery attack

You shouldn't ignore the frequent Node.js security vulnerability known as Cross-Site Request Forgery (CSRF). Using a CSRF attack, a web application against which a user has already been authenticated is forced to accept requests from authenticated users. It lets hackers access private information and risks web applications' security and integrity.

Hackers utilizing CSRF want to alter the application's state by tricking users into thinking they've received a message or email. Attacks on admin-level users that use CSRF could put the security of the whole web application at risk.

Cookies

Since every user action on a web application results in cookies kept in the underlying infrastructure, cookies help websites or web apps identify a specific user. The most typical uses of cookies are in shopping carts on eCommerce websites. 

Because of the cookies, the shopping cart will show you the items you previously selected on the website when you go to the checkout page.

However, the issue with Node.js development is when the developer chooses the standard cookie names rather than changes them to meet the needs. Since hackers know the default cookie name, they are likely to attack and quickly get access to user input.

Brute-force attacks

One of the most frequent threats or risks in any Node.js security checklist is brute force attacks. To gain access to sensitive data, hackers attempt to use random passwords generated at the login endpoints of web applications. 

The goal of brute forcing is to try millions of different password combinations until they find the one that works for the online application. You must fortify your authentication system for Node.js applications to thwart brute-force attacks. 

To deal with such unsafe scenarios, you can also restrict the number of login attempts from one IP address and use bcrypt.js to protect the passwords saved in the database.

X-powered-By header

Many programming languages, by default, use the non-standard HTTP response header known as X-Powered-By. This header identifies the technology used in app development. 

It enables hackers to take advantage of numerous security flaws related to that specific technology. You can enable or disable this header using server configuration management approaches.  

Distributed Denial of Service

A Distributed Denial of Service (DDoS) assault involves flooding a server, service, or network with excessive internet data. It includes malicious JavaScript code to interfere with the regular server, service, or network traffic. 

Due to their ability to take advantage of the HTTP processing flaw, Node.js versions 4.0.0 and 4.1.1 have given birth to DDoS attacks.

Since they have the potential to destroy your servers, networks, or services, limiting these attacks is crucial to guarantee the smooth functioning of your Node.js apps.  

Cross-Site Scripting Attack

Cross-site scripting attacks are critical risks to be aware of when developing Node.js web applications. Cross-site scripting (XSS) lets attackers change the JavaScript code in a web app using client-side scripting.  

The end user's browsers cannot determine whether the codebase is trustworthy. Therefore, a hacker can use XSS to send them a malicious script. 

As a result, they automatically execute it, allowing attackers to access any session tokens, cookies, or other private data. These scripts can also change any HTML page's content, making XSS extremely dangerous.

Let's break down Node.js's recommended practices to help you avoid these situations now that you're sufficiently aware of their risks.

Best ways to boost Node.JS application security 

Validation of user data

The data coming from the user or another system entity must constantly be verified. The working system is endangered by poor validation, resulting in security exploits. Data validation can be carried out using a node module named validator. Let's explore Node.js's data validation capabilities.  


const validator = require('validator');

validator.isEmail(This email address is being protected from spambots. You need JavaScript enabled to view it.'); //=> true

validator.isEmail('bar.com'); //=> false

The data/schema validation can also be done using a module named joi.  


const joi = require('joi');

  try {

    const schema = joi.object().keys({

      name: joi.string().min(3).max(45).required(),

      email: joi.string().email().required(),

      password: joi.string().min(6).max(20).required()

    });

    const dataToValidate = {

        name: "Shahid",

        email: "abc.com",

        password: "123456",

    }

    const result = schema.validate(dataToValidate);

    if (result.error) {

      throw result.error.details[0].message;

    }    

  } catch (e) {

      console.log(e);

  }

Prevent SQL injection attacks

Your database can be retrieved, added to, or modified by attackers who defeat authentication. SQL injections happen when you ask users for input, such as their id or username, and they insert a SQL statement in its place. This is a typical hacking tactic that could ruin your database.

See one example below.


textuserID = getRequestString("userID");

textSQL = "SELECT * FROM Users WHERE userID = " + textuserID;

Let's examine some of the potential problems. The example retrieves a variable (textuserID) from user input. The user is chosen in the following line of code based on their ID.

SQL injection 1=1

The SQL statement appears as follows if your user provides something like 100 OR 1=1 for their userID:


SELECT * FROM Users WHERE userID = 100 OR 1=1;

Since 1=1 is true, the statement above will return all rows from the Users table. This might be pretty risky if your table contains data like usernames and passwords.

How to prevent SQL injection?

  • Parameterized statements: Regardless of the input, they enable your database to distinguish between data and code.
  • Input validations: Additional protection is provided via input validations. Writing your validation logic will allow you to compare input to a list of permitted possibilities.
  • Database with least privilege: Your app is protected from SQL injections if you use database accounts with the very minimum access. Avoid using database accounts with admin permissions in your Node.js application.
  • Sanitize input: You must make sure to remove any input that seems suspect. Checking fields like email addresses, and matching a regular expression might help you achieve that.

Data verification through typecasting

Since JavaScript is dynamically typed, a value can be of any type. To ensure that only the appropriate value enters the database, you can use the typecasting technique to determine the data type. For instance, typecasting should ensure that a user ID can only be a number and can only accept numbers. As an illustration, consider the code we displayed below.


var mysql = require('mysql');

var connection = mysql.createConnection({

  host     : 'localhost',

  user     : 'me',

  password : 'secret',

  database : 'my_db'

});

connection.connect();

connection.query(

    'UPDATE users SET ?? = ? WHERE ?? = ?',

    ['first_name',req.body.first_name, ,'id',Number(req.body.ID)],

    function(err, result) {

    //...

});

In order to verify that ID is always a number, we used Number(req.body.ID). 

Authentication and authorization

Passwords and other sensitive data should be maintained in systems securely to prevent misuse by malicious individuals. We will learn how to save and manage passwords in this part.  

Password hashing

Hashing is a form of one-way encryption. Because it lacks a decryption key, it differs from encryption. Assume your password is Pa55word to see how it works. It will resemble dhkqhuhdhudhuh after being processed by a hashing method

You can sign in by entering your password (Pa55word), which is hashed and checked to see whether it matches dhkqhuhdhudhuh.

To protect passwords, the Node.js package bcryptjs takes the password and the salt, which says how many times the hashing method should run.

The hash and salt are generated using several function calls in the example below.


bcrypt.genSalt(saltRounds, function(err, salt) {

    bcrypt.hash(myPlaintextPassword, salt, function(err, hash) {

        // Store hashed password in database

    });

});

Storage

You cannot keep a plain text copy of your passwords, whether you use a database or files to store them. You should create the hash and store it in the system.

In cases when a password is involved, we typically advise utilizing the varchar(255) data type. You can choose a field with an unlimited length. You can utilize the varchar(60) field using bcrypt because it creates fixed-size hashes of 60 characters.

Encryption

By using an encryption key, password encryption enables you to turn your passwords into an unreadable message. The recipient and you both know the mathematical value that serves as the encryption key.

The recipient converts the random text to a message that can be read using the encryption key. Encryption is a two-way function. Therefore, you must afterwards decrypt everything you encrypt.

User authorization

A system with appropriate user roles and permissions stops malicious users from acting without authorization. Each user is given the appropriate roles and rights to establish a proper authorization procedure, allowing them to perform only necessary actions. Using the well-known ACL module, you can create access control lists based on system permission in Node.js.


const ACL = require('acl2');

const acl = new ACL(new ACL.memoryBackend());

// guest is allowed to view blogs

acl.allow('guest', 'blogs', 'view')

// check if the permission is granted

acl.isAllowed('joed', 'blogs', 'view', (err, res) => {

    if(res){

        console.log("User joed is allowed to view blogs");

    }

});

Stop brute-force attacks

In brute force attacks, the attackers always continue to attempt to create a random password. Attackers in this situation might think about generating millions of passwords until they come up with the ideal one.

Consider utilizing the bcrypt.js package, which will protect the password whenever it is saved in the database. Additionally, you can think about restricting the volume of queries from a single IP.

Execute the below code:


npm install express-brute --save

Try this code:


const ExpressBrute = require('express-brute');

const store = new ExpressBrute.MemoryStore();  

const bruteforce = new ExpressBrute(store);

app.post('/auth',

    bruteforce.prevent, // error 429 if we hit this route too often

    function (req, res, next) {

        res.send('Success!');

    }

);

SSL security layer 

Browsers and search engines use digital certificates, known as SSL certificates, to verify the legitimacy of websites. SSL certificates are one of the most important things to consider when protecting your web apps. Anyone can mimic your website without an SSL certificate and steal vital user information.

We must take the actions listed below to generate the SSL Certificate:

  • Establish a Private Key
  • Using the private key, generate a CSR (certificate signing request).
  • Create an SSL certificate using a CSR

Integrate session layers

To manage sessions in Node.js, we can use express-session middleware. In the express server itself, the session is kept. MemoryStore, the standard server-side session storage, is not intended for use in a real-world setting. It does not grow beyond a single process, leaks memory frequently, and is designed for debugging and development. 

We must establish a global map and add each session object to manage numerous sessions for multiple users. Global variables in Node.js consume a lot of memory and present severe security risks in applications that are intended for production environments.

Utilizing an external session store will help you resolve this. Each session must be saved in the store so that it only ever belongs to one user. Redis is a widely used session store.

CSRF attack prevention

End users are compelled to do unnecessary activities on authenticated web apps by CSRF attacks. Due to the attacker's lack of access to the falsified request response, CSRF attacks target requests for changes in the application state.

 State-changing requests can be forced using CSRF. The entire online application may be compromised for administrative users if CSRF occurs.

Anti-Forgery Tokens are necessary for Node.js to prevent CSRF. Tokens that are anti-CSRF are used to keep track of user requests, confirm their legitimacy, stop one-click attacks, and more.

Request size reduction for DDoS

The first thing to think about when dealing with DOS attack defense is restricting the actual payload that users can submit to your API/app/service. You can limit the body payload by using a body parser. You can make use of ExpressJS's built-in body parser.


const express = require('express');

const app = express();app.use(express.json({ limit: '10kb' })); // Body limit is 10

Prioritize MongoDB access

Information is kept in MongoDB as JSON documents. It offers assistance for all fundamental data types. However, MongoDB saves them as BSON (binary-encoded JSON documents). Because it encrypts special characters, MongoDB defends itself against conventional injection attacks like BSON documents.  

Appropriate error handling

When addressing errors, there are a few things to think about. First, don't reveal the information to the user, i.e., don't provide the client with the entire error object. It can have data you don't want to make public, such as pathways, a different library being used, or even secrets. 

Second, encapsulate routes in a catch clause to prevent Node.js from crashing when a request causes an error. By doing this, attackers are prevented from discovering malicious requests that would crash your application and repeatedly submitting them, causing your program to crash.

Securing cookies

A cookie stores the details of every activity you take on the website.

The site's session cookie keeps track of the options you've made. The new page won't be able to identify your prior actions on other pages without session cookies.

Utilizing default cookie names puts your application at risk because attackers can easily detect and use them against you. Use express-session or another middleware cookie session module to fix the issue.

Security headers implementation

There are many security headers available in HTTP that can prevent well-known attacks. To enable all security headers with a single line of code while utilizing the Express framework, use the helmet module.

npm install helmet --save

Try this code to use this.


const express = require("express"); 

const helmet = require("helmet");  

const app = express(); 

app.use(helmet());  

Conclusion

Security flaws and threats have cost businesses thousands of dollars over the years. While data breaches can be costly, sensitive data leaks and stolen information are valuable. We might be unable to stop every attempt an attacker might make to destroy our apps. Still, we can control how much harm our carelessness causes.

This article discusses security throughout the entire software development lifecycle, not only the best practices that should be followed when creating applications. You can opt to hire a Node.js development company or consultants if you want to secure your Node.js application. Or need assistance creating data-intensive apps tailored to your company's requirements.