Enhance Security for NodeJS Application

1. Limiting the number of requests

Limiting the number of requests (from a single IP address within a specific timeframe) is a method used to prevent denial-of-service (DOS, DDOS) attacks or brute force attacks that could overload your server.

If you're using Express, integrating this is quite straightforward using the express-rate-limit package.

import * as express from 'express'
import helmet from 'helmet'
import expressRateLimit from 'express-rate-limit'

const app = express()
const limiter = expressRateLimit({
windowMs: 10 * 60 * 1000, // ms, ~10 minutes
max: 50, // limit each IP to 50 requests
})
const specificLimiter = expressRateLimit({
windowMs: 60 * 60 * 1000, // 1 hour window
max: 2, // start blocking after 2 requests
message: 'Too many requests', // default 429 TOO MANY REQUESTS
})

app
.use(limiter) // use for all route
.use('/common', (req, res) => {
res.json({api: 'common', ok: true})
})
.get('/specific-api', specificLimiter, (req, res) => { // use middleware for specific route
res.json({api: 'specific', ok: true})
})
.listen(3000)

Note that if your development environment involves numerous test cases requiring requests to various APIs, it's advisable to exclude the rate limit. Always check the environment before starting the server to ensure that the rate limit is added in the production environment (or any desired environments).


2. Limiting the payload request size

Limiting the payload size sent to the server helps prevent DOS/DDOS attacks. Fortunately, Express also provides us with this capability as follows:

import * as express from 'express'

const app = express()
app
.use(express.json({limit: '50kb'})) // body-parser defaults to a body size limit of 100kb
.post('/send-data', (req, res) => {
if (!req.is('json')) {
return res.sendStatus(400) // Bad request
}
res.json({ok: true})
})
.listen(3000)


3. Using Helmet

Helmet is an npm package that includes middleware to handle and filter out malicious request headers (exploiting XSS vulnerabilities or clickjacking, for example). You can utilize Helmet's default configuration or customize it based on your needs following the instructions provided here.

Using it is as simple as:

import * as express from 'express'
import helmet from 'helmet'

const app = express()
app
.use(helmet())
.listen(3000)


4. Validate User Data Sent to the Server

Even if data validation is performed solely on the frontend, it's crucial to validate it on the backend as well. Frontend validation primarily serves regular users, while hackers may exploit vulnerabilities like SQL Injection to directly attack the server. Check out the following article for guidance on using express-validator to validate request data. It's a simple yet effective solution to enhance your server's security.


5. Use ORM Libraries (Object-Relational Mapping)

Utilize packages like TypeORM or Sequelize to mitigate serious security risks like SQL/NoSQL Injection. These packages offer convenient and secure functions suitable for connecting to multiple databases or migrating to different databases without significantly altering logic. While there are debates about the impact of using these packages on system performance, you can enhance performance through various methods. Moreover, considering the security risks, prioritizing this aspect is essential.

Avoid concatenating strings when querying to prevent hidden security risks:

const idValue = req.id; // get id from request so that attackers can attach malicious code
const query = `SELECT * FROM table_name a WHERE a.id = ${idValue};`;


6. Avoid Storing Secret Info Directly in the Codebase

Sensitive information like secret keys, database passwords, API keys, and other crucial data should never be stored directly in the source code. Instead, place these details in a .env file (and remember to add the .env file to .gitignore). Here's a simple way to do it:

Install the dotenv package.

yarn add dotenv


Creating a .env file

PASSWORD=password


Accessing Value

process.env.PASSWORD


Additionally, you can utilize Key Management Service (KMS) to store secret information on the cloud. Many providers like AWS, GCP, etc, offer support. Each method, whether using a .env file or KMS, has its pros and cons, so it's essential to weigh your options and choose what suits your needs best.


7. Choosing an Algorithm to Hash Passwords

Some hashing functions like SHA1, MD5, SHA-256 have been around for a while, but their hash/second rate is low, making them unsuitable for password hashing. Instead, opt for algorithms with a high hash/second rate like bcrypt or pbkdf2 to generate more complex hash strings. This will make it significantly more challenging and nearly impossible for attackers to crack passwords, as it will require a considerable amount of time and effort.

I'll provide a simple example using bcrypt to hash and compare passwords as follows:

import * as bcrypt from 'bcrypt'

const saltRounds = 10,
password1 = 'password1',
password2 = 'password2'

// 2 ways to gen hash
const hash1: string = await new Promise(rs =>
bcrypt.genSalt(saltRounds, (err, salt) => {
bcrypt.hash(password1, salt, (err, hash) => {
rs(hash) // store hash in your password DB.
})
})
)
const hash2 = await bcrypt.hash(password2, saltRounds)

// load hash from your password DB to compare
bcrypt.compare(password1, hash1, (err, result) => {
console.log(password1, hash1, result)
})
const result = await bcrypt.compare(password2, hash2)
console.log(password2, hash2, result)


8. Using HTTPS

When you use HTTP (Hypertext Transfer Protocol), data sent to the server isn't encrypted and can be intercepted and read by hackers. Nowadays, browsers like Chrome, Firefox, etc., display warnings when you access websites using HTTP. This can diminish your website's credibility to users and affect its ranking on search engines.

Using HTTPS (HTTP Secure) means that data sent to the server is encrypted, making it difficult for attackers to exploit sensitive information from those requests. However, it's important to note that accessing a website with HTTPS doesn't guarantee complete security.

To implement HTTPS for your website quickly and easily, you can use Cloudflare. It's a popular third-party service that acts as a proxy between users and your server, providing support for certificates and encryption. Simply sign up for a free Cloudflare account and configure details like your IP address and domain name. Since it's a third-party service, access speed may be slightly slower than usual.


Conclusion

Through this article, you've learned some methods to enhance security for your NodeJS application. These methods are relatively straightforward and can be implemented right away in your current project.

Don't hesitate to leave your thoughts in the comments section, and remember to like, share, and follow for more insightful content in the future!

Comments

Popular posts from this blog

Kubernetes Practice Series

NodeJS Practice Series

Docker Practice Series

React Practice Series

Sitemap

Setting up Kubernetes Dashboard with Kind

Explaining Async/Await in JavaScript in 10 Minutes

Deploying a NodeJS Server on Google Kubernetes Engine

Create API Gateway with fast-gateway

DevOps Practice Series