< Back to blog
PUBLISH DATE:
Share on:
1442Express.js Security Tips to Save Your AppOleg RomanyukHead of Inbound Marketing

Express.js Security Tips to Save Your App

Is Scrum as universal as it seems to be?

Is your phone locked? Do you have a pin-code, password, fingerprint, or FaceID? I am 99 percent sure that you do. And it is clear why – you care about your safety. Nowadays, keeping your phone protected is as important as brushing your teeth in the morning. But what can be done to protect your Express.js project, too?

To understand the need for data protection, you don’t necessarily have to have software development experience – having a mobile phone is enough. Yes, everyone possessing a mobile phone realizes the importance of information security. But for those engaged in software development, this aspect becomes doubly important. 

Responsible and mindful software developers know that keeping their apps secure is as vital as protecting their phones. If you are a developer and choose to neglect it – please, reconsider your approach. If you are a project owner and your development team says that data safety can wait, please, reconsider your team.

In this article, I want to talk about the ways to make sure that your Node JS Express project is safe and invincible to malicious attacks. In case you are interested in this topic but have no experience yet, here you can learn what Express JS is and how it can be applied to your case.

You might ask, “But what is Node JS Express in business? What kind of companies use it?” To understand how the Node JS Express example works in practice, consider Instapage or Brinker International, Inc. As their cases show, the advantage of Express.js valued by developers the most is the simplicity of configuration and customization. 

But let’s move to our topic. To be brief, there are 7 simple (and not very simple) measures to take for the purpose of data security:

  1. Use reliable versions of Express.js
  2. Secure the connection and data
  3. Protect your cookies
  4. Secure your dependencies
  5. Validate the input of your users
  6. Protect your system against brute force
  7. Control user access

Now, we will have a closer look at each.

1. Use reliable versions of Express.js

Keep in mind the following: deprecated or outdated versions of Node Express are no longer an option. Neither are the software development companies that work with obsolete frameworks. For instance, the 2nd and 3rd versions of Express are not supported today. It means that in these, safety or performance issues are not fixed anymore.

As a developer, you absolutely have to migrate to Express 4. This version is a revolution! It is quite different in terms of the routing system, middleware, and other minor aspects. Nonetheless, using Express 4, you can be sure that your product has the highest-level data protection and your client feels safe.

Meantime, if you are a client, choose software developers wisely. One of the characteristics of dedicated professionals is their readiness to learn new things and improve their competence. Moving to the latest version should not pose a challenge for a skilled developer. If it posses though, maybe it’s high time to look for a more qualified team.

2. Secure the connection and data

To secure HTTP headers, make use of Helmet.js – a helpful Node.js development module. It is a collection of 13 middleware functions for setting HTTP response headers. In particular, there are functions for setting Content Security Policy, handling Certificate Transparency, preventing clickjacking, disabling client-side caching, or adding some small XSS protections. 

1
npm install helmet --save

Even if you don’t want to use all the functions of Helmet, the absolute minimum that you must do is to disable X-Powered-By header:

1
app.disable('x-powered-by')

This header can be used to detect that the application is powered by Express, which lets hackers conduct a precise attack. Surely, X-Powered-By header is not the only way to identify an Express-run application, but it is probably the most common and simple one.

To protect your system from HTTP parameter pollution attacks, you can use HPP. This middleware puts aside such parameters as req.query and req.body and selects the latest parameter value instead. The installation command looks as follows:

1
npm install hpp --save

To encrypt data, which is being sent from the client to the server, use Transport Layer Security (TLS). TLS is a cryptographic protocol for securing the computer network, the descendant of the Secure Socket Layer (SSL) encryption. TLS can be handled with Nginx – a free but effective HTTP server – and Let’s Encrypt – a free TLS certificate.

3. Protect your cookies

In Express.js 4, there are two cookie session modules:

  • express-session (in Express.js 3, it was express.session)
  • cookie-session (in Express.js 3, it was express.cookieSession)

The express-session module stores session ID in the cookie and session data on the server. The cookie-session stores all the session data to the cookie. 

In general, cookie-session is more efficient. Yet, if the session data you need to store is complex and likely to exceed 4096 bytes per cookie, use express-session. Another reason to use express-session is when you need to keep the cookie data invisible to a client. 

Besides, you should set cookie security options, namely: 

  • secure
  • httpOnly
  • domain
  • path
  • expires

If “secure” is set to “true”, the browser will send cookies only via HTTPS. If “httpOnly” is set to “true”, the cookie will be sent not via client JS but via HTTP(S). The value of “domain” indicates the domain of the cookie. If the cookie domain matches the server domain, “path” is used to indicate the cookie path. If the cookie path matches the request path, the cookie will be sent in the request. Finally, as the name itself suggests, the value of “expires” stands for the time when the cookies will expire.

Another important recommendation is not to use the default session cookie name. It may enable hackers to detect the server and to run a targeted attack. Instead, use generic cookie names.

4. Secure your dependencies

No doubt, npm is a powerful web development tool. However, to ensure the highest level of security, consider using only the 6th version of it – npm@6. The older ones may contain some severe dependency safety vulnerabilities, which will endanger your entire app. Also, to analyze the tree of dependencies, use the following command:

1
npm audit

npm audit can help to fix real problems in your project. It checks all your dependencies in dependencies, devDependencies, bundledDependencies, and optionalDependencies, but not in peerDependencies. Here you can read about all current vulnerabilities in any of npm packages.

Another tool to ensure dependency safety is Snyk. Snyk runs the application check to identify whether it contains any vulnerability listed in Snyk’s open-source database. To conduct the check, run three simple steps.

  • Step 1. Install Snyk
1
2
npm install -g snyk
cd your-app
  • Step 2. Run a test
1
snyk test
  • Step 3. Learn how to fix the issue
1
snyk wizard

Wizard is a Snyk method that explains the nature of the dependency vulnerability and offers ways of fixing it.

5. Validate the input of your users

Controlling user input is an extremely important part for server-side development. This is a no less important problem than unauthorized requests, which will be described in the seventh part of this article.

First of all, wrong user input can break your server when some values are undefined and you do not have error handling for a specific endpoint. However, different ORM systems can have unpredictable behavior when you try to set undefined, null, or other data types in the database.

For example, destroyAll method in Loopback.js ORM (Node.js framework) can destroy all data in a table of the database: when it does not match any records, it deletes everything as described here. Imagine that you can lose all data in a production table just because you have ignored input validation. 

  • Use body/object validation for intermediate inspections

To start with, you can use body/object validation for intermediate inspections. Below, you can see the Express JS example of ajv validator, which is the fastest JSON Schema validator for Node.js.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
const Ajv = require('ajv'); 
const ajv = new Ajv({allErrors: true}); 
const speaker = { 
  'type': 'object', 
  'required': [
    'id', 
    'name'
  ],
  'properties': { 
    'id': {
      'type': 'integer', 
    }, 
    'name': { 
      'type': 'string',
    }, 
  }, 
};
const conversation = { 
  type: 'object', 
  required: [
    'duration', 
    'monologues'
  ], 
  properties: { 
    duration: { 
      type: 'integer',
    }, 
    monologues: { 
      type: 'array', 
      items: monolog, 
    }, 
  }, 
};
const body = { 
  type: 'object', 
  required: [
    'speakers', 
    'conversations'
  ], 
  properties: { 
    speakers: { 
      type: 'array', 
      items: speaker, 
    }, 
    conversations: { 
      type: 'array', 
      items: conversation, 
    }, 
  }, 
}; 
const validate = ajv.compile(body); 
const isValidTranscriptBody = transcriptBody => { 
  const isValid = validate(transcriptBody);
  if (!isValid) { 
    console.error(validate.errors); 
  } 
  return isValid; 
};
  • Handle errors

Now, imagine that you forgot to check a certain object and you do some operations with the undefined property. Or you use a certain library and you get an error. It can break your instance, and the server will crash. Then, the attacker can ping a specific endpoint where there is this vulnerability and can stop your server for a long time. The simplest way to do an error handling is to use try-catch construction:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
try { 
  const data = body;
  if (data.length === 0) throw new Error('Client Error'); 
  const beacons = await  this.beaconLogService.filterBeacon(data); 
  if (beacons.length > 0) { 
    const max = beacons.reduce((prev, current) => (prev.rssi > current.rssi) ? prev : current); 
    await this.beaconLogService.save({ 
      ...max,
      userId: headers['x-uuid'] 
    }); 
    return { 
      data: { 
        status: 'Saved', 
        position: max 
      }, 
    }; 
  } 
  return { 
    data: { 
      status: 'Not valid object, 
    }, 
  }; 
} 
catch(err) { 
  this.logger.error(err.message, err.stack); 
  throw new HttpException('Server Error',     HttpStatus.INTERNAL_SERVER_ERROR); 
}

Feel free to use a new Error(‘message’) constructor for error handling or even extend this class for your own purpose!

  • Use JOI

The main lesson here is that you should always validate user input not to fall victim to man-in-the-middle attacks. Another way to do it is with the help of @hapi/joi – a part of the hapi ecosystem and a powerful JS data validation library. 

Pay attention here that the module joi has been deprecated. For this reason, the following command is no go:

1
npm install joi

Instead, use this one:

1
npm install @hapi/joi
  • Sanitize user input

Also, an important measure to take is sanitizing user input to protect the system from a MongoDB operator injection. For this, you should install and use express-mongo-sanitize:

1
npm install express-mongo-sanitize
  • Protect your app against CSRF

Besides, you should protect your app against cross-site request forgery (CSRF). CSRF is when unauthorized commands are sent from a trusted user. You can do this with the help of csurf. Prior to that, you need to make sure that session middleware for cookies is configured as described earlier in this article. To install this Node.js module, run the command:

1
npm install csurf 

6. Protect your system against brute force

A brute force attack is the simplest and most common way to get access to a website or a server. The hacker (in most cases automatically, rarely manually) tries various usernames and passwords repeatedly to break into the system.

These attacks can be prevented with the help of rate-limiter-flexible package. This package is fast, flexible, and suitable for any Node framework. 

To install, run the following command:

1
2
npm i --save rate-limiter-flexible
yarn add rate-limiter-flexible

This method has a simpler but more primitive alternative: express-rate-limit. The only thing it does is limiting repeated requests to public APIs or to password reset.

1
npm install --save express-rate-limit

7. Control user access

Among the authentication methods, there are tokens, Auth0, and JTW. Let’s focus on the third one! JTW (JSON Web Tokens) are used to transfer authentication data in client-server applications. Tokens are created by the server, signed with a secret key, and transferred to a client. Then, the client uses these tokens to confirm identity.

Express-jwt-permissions is a tool used together with express-jwt to check permissions of a certain token. These permissions are an array of strings inside the token:

1
2
3
4
5
"permissions": [
  "status",
  "user:read",
  "user:write"
]

To install the tool, run the following command:

1
npm install express-jwt-permissions --save

To Wrap Up

In this article, I have listed the essential Node Express security practices and some tools that can be used along the way. For you to sum up:

Remember: information safety is one of the core values today. I strongly recommend that you make sure that your application is resistant to malicious attacks. Otherwise, your business and your users may suffer significant losses. Hope that my article has made your way to data security a little bit easier. Good luck!

Do you have an idea for an Express.js app?

IRead more about Express.js development services that we offer.

A huge shout-out to Volodia Andrushchak, Full-Stack Software Developer at @KeenEthics, for helping me with the article.

Share on: