Node.js good practices for security

Tech with Harry
6 min readMar 26, 2023

--

Security is an essential aspect of any application, and Node.js applications are no exception. Node.js is a powerful tool that can be used to build secure and robust web applications, but it’s important to understand the security risks that come with building applications using Node.js. Here are some potential topics you could cover in an article on Node.js security best practices:

1. SQL Injection

SQL injection is a type of attack where an attacker injects malicious SQL code into an application to gain access to sensitive data or execute unauthorized actions. To prevent SQL injection in a Node.js application, you can use parameterized queries or prepared statements, which separate the query logic from the user input. Parameterized queries and prepared statements ensure that the user input is treated as a value rather than as part of the query syntax, which prevents attackers from injecting malicious code into the query. A simple example is shown below.

const mysql = require('mysql');

// Set up a connection to the database
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'mydatabase'
});

// Define a function to get user data from the database
function getUser(username, password, callback) {
const query = 'SELECT * FROM users WHERE username = ? AND password = ?';
const values = [username, password];

// Use a parameterized query to prevent SQL injection
connection.query(query, values, (error, results, fields) => {
if (error) {
callback(error, null);
} else {
callback(null, results);
}
});
}

In this example, the getUser() function takes a username and password as arguments and returns user data from the database. To prevent SQL injection, the function uses a parameterized query by replacing the values in the SQL statement with placeholders (?) and passing the actual values as an array (values) to the connection.query() method. This ensures that the user input is treated as a value rather than as part of the SQL syntax, which prevents SQL injection attacks.

Additionally, using an ORM (object-relational mapping) library like Sequelize or TypeORM can provide an additional layer of protection against SQL injection by automatically escaping user input.

2. Cross-site scripting (XSS)

Cross-site scripting is a type of attack where an attacker injects malicious scripts into a web page viewed by other users. This can be used to steal sensitive data or execute unauthorized actions. To prevent XSS in a Node.js application, you can use input validation and sanitization to ensure that user input is free of malicious scripts. You can also use a content security policy (CSP) to control which external resources (such as scripts and stylesheets) are loaded by your application. A simple example is shown below.

const express = require('express');
const bodyParser = require('body-parser');
const xss = require('xss');

const app = express();

// Use body-parser middleware to parse incoming request bodies
app.use(bodyParser.json());

// Define a route to handle a POST request
app.post('/submit', (req, res) => {
// Get the user input from the request body
const userInput = req.body.input;

// Validate and sanitize the user input using the xss library
const sanitizedInput = xss(userInput);

// Save the sanitized input to the database or perform other actions
// ...

res.send('Input saved successfully!');
});

// Start the server
app.listen(3000, () => {
console.log('Server started on port 3000');
});

In this example, the server uses the xss library to validate and sanitize user input received in a POST request. The xss library is specifically designed to prevent Cross-site scripting (XSS) attacks by escaping any HTML, JavaScript, or other potentially malicious code in the user input.

3. Cross-site request forgery (CSRF)

Cross-site request forgery is a type of attack where an attacker tricks a user into performing an action on a website without their knowledge or consent. To prevent CSRF in a Node.js application, you can use tokens to ensure that requests are coming from a trusted source. Tokens are typically generated on the server and included in each request. The server then verifies the token before executing the requested action. A simple example is shown below.

const express = require('express');
const bodyParser = require('body-parser');
const csrf = require('csurf');

const app = express();

// Use body-parser middleware to parse incoming request bodies
app.use(bodyParser.json());

// Set up CSRF protection using the csurf middleware
const csrfProtection = csrf({ cookie: true });

// Define a route to render a form with a CSRF token
app.get('/form', csrfProtection, (req, res) => {
res.render('form', { csrfToken: req.csrfToken() });
});

// Define a route to handle a POST request
app.post('/submit', csrfProtection, (req, res) => {
// Verify the CSRF token
if (req.csrfToken() !== req.body._csrf) {
res.status(403).send('Invalid CSRF token!');
return;
}

// Save the form data to the database or perform other actions
// ...

res.send('Form data saved successfully!');
});

// Start the server
app.listen(3000, () => {
console.log('Server started on port 3000');
});

In this example, the server uses the csurf middleware to generate and validate CSRF tokens. When the user visits the /form route, the server generates a CSRF token using the req.csrfToken() method and renders a form that includes the token as a hidden input field. When the user submits the form, the server verifies that the CSRF token in the request body matches the token generated earlier using the req.csrfToken() method. If the tokens don't match, the server returns a 403 Forbidden response.

By using CSRF tokens like this, you can help prevent Cross-site request forgery (CSRF) attacks in your Node.js application. It’s also important to set the SameSite attribute on any cookies used by your application to ensure that they're not sent with cross-site requests. Additionally, you should avoid using HTTP methods with side effects (such as POST and DELETE) for GET requests, and avoid relying solely on cookies for authentication and authorization.

4. Denial-of-service (DoS) attacks

DoS attacks are a type of attack where an attacker floods a server with requests in an attempt to overwhelm it and make it unavailable to users. To prevent DoS attacks in a Node.js application, you can use rate limiting to limit the number of requests that can be made in a certain period of time. You can also use load balancers and server clusters to distribute the workload across multiple servers. A simple example is shown below.

const express = require('express');
const rateLimit = require('express-rate-limit');

const app = express();

// Set up rate limiting using the express-rate-limit middleware
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later'
});

// Apply rate limiting to all routes
app.use(limiter);

// Define a route to handle a GET request
app.get('/', (req, res) => {
res.send('Hello, world!');
});

// Start the server
app.listen(3000, () => {
console.log('Server started on port 3000');
});

In this example, the server uses the express-rate-limit middleware to set limits on incoming requests from each IP address. The limiter object specifies that each IP address can make a maximum of 100 requests within a 15-minute window. If a client exceeds this limit, the server returns a 429 Too Many Requests response with the message "Too many requests from this IP, please try again later".

By setting limits on incoming requests like this, you can help prevent Denial-of-service (DoS) attacks in your Node.js application. It’s also a good practice to use a load balancer or proxy server to distribute incoming requests across multiple servers and to implement connection timeouts and request timeouts to prevent long-running requests from tying up server resources. Additionally, you should monitor your server logs for unusual or suspicious activity and implement DDoS protection solutions if your application is particularly vulnerable to DoS attacks.

In conclusion, the above sections are the simple but efficient ways to secure your nodejs application. There are more things to consider while developing the application such as using secure coding practices like avoiding eval or implementing secure data storage with proper user authentication and authorization process.

--

--

Tech with Harry

Fullstack Software Engineer | Proficient in Web development and Cloud technologies ☁️