Security is really hard to get right. There are so many different factors to consider, countless different ways to break an application. As a Node.js developer, you most work with a huge amount of user data, probably some of them being very sensitive. We’ve made this list of tips that will help harden Express applications against different kinds of security vulnerabilities.

Note: This guide is not meant to address every single possible security flaw within an Express application.

Limit concurrent requests

DOS attacks are very popular and relatively easy to conduct. Implement rate limiting using an external service such as cloud load balancers, cloud firewalls, nginx, or (for smaller and less critical apps) a rate limiting middleware like express-rate-limit .

Extract secrets from config files

Never store plain-text secrets in configuration files or source code. Instead, make use of secret-management systems or environment variables, secrets stored in source control must be encrypted and managed. Make use of .gitignore to prevent committing secrets accidentally.

Check for Known Security Vulnerabilities

There are a few tools in the Node ecosystem that allow easy checking for vulnerabilities in Node and Express application dependencies. These tools are highly valuable in ensuring that no vulnerabilities are currently in the packages an application relies on, and none are added into that application when its packages are updated, like Synk .

Prevent query injection with ORM/ODM

To prevent SQL/NoSQL injection and other attacks, always use an ORM/ODM or a database library that escapes data, and takes care of validating user input for expected types. Never just use JavaScript template strings or string concatenation to inject values into queries. All the reputable Node.js data access libraries like Sequelize , Knex , Mongoose have built-in protection agains injection attacks.

Adjust the HTTP response headers

Your application should be using secure headers to prevent attackers from using common attacks like Cross-Site-Scripting (XSS), clickjacking and other malicious attacks. These can be configured easily using modules like helmet .

Helmet can help protect your app from some well-known web vulnerabilities by setting HTTP headers appropriately. Is actually just a collection of smaller middleware functions that set security-related HTTP response headers:

  • csp sets the Content-Security-Policy header to help prevent cross-site scripting attacks and other cross-site injections.
  • frameguard sets the X-Frame-Options header to provide clickjacking protection.
  • hidePoweredBy removes the X-Powered-By header.
  • hpkp Adds Public Key Pinning headers to prevent man-in-the-middle attacks with forged certificates.
  • hsts sets Strict-Transport-Security header that enforces secure (HTTP over SSL/TLS) connections to the server.
  • ieNoOpen sets X-Download-Options for IE8+.
  • noCache sets Cache-Control and Pragma headers to disable client-side caching.
  • noSniff sets X-Content-Type-Options to prevent browsers from MIME-sniffing a response away from the declared content-type.
  • xssFilter sets X-XSS-Protection to enable the Cross-site scripting (XSS) filter in most recent web browsers.

Helmet supports a lot more headers, to get the full list, visit the (official documentation of helmet .

Block Cross-Site Request Forgeries

An attacker can attempt to put data into an application via their own site through a common phishing technique that uses Cross-Site request forgeries. An attacker making a phishing attempt can create a request that creates a request against an application, through the forms, data, or other input an application has exposed.

This can be mitigated with a CSRF token implementation, essentially, every time the user makes a request a new CSRF token is generated and added to the user’s cookie. To effectively prevent against CSRF attacks, that token should be added as a value to inputs in an application’s templates and will be checked against the token that the CSRF library, such as csurf generated when the user sends information.

Use cookies securely

Don’t use the default session cookie name and set cookie security options appropriately. Use the express-session ) middleware stores session data on the server, it only saves the session ID in the cookie itself, not session data. By default, it uses in-memory storage and is not designed for a production environment, for that reason in production you’ll need to set up a scalable session-store.

Set the following cookie options to enhance security:

  • domain - indicates the domain of the cookie; use it to compare against the domain of the server in which the URL is being requested. If they - match, then check the path attribute next.
  • expires - use to set expiration date for persistent cookies.
  • httpOnly - Ensures the cookie is sent only over HTTP(S), not client JavaScript, helping to protect against cross-site scripting attacks.
  • path - indicates the path of the cookie; use it to compare against the request path. If this and domain match, then send the cookie in the - request.
  • secure - Ensures the browser only sends the cookie over HTTPS.

Escape HTML, JS and CSS output

Untrusted data that is sent down to the browser might get executed instead of just being displayed, this is commonly being referred as a Cross-Site-Scripting (XSS) attack. Mitigate this by using dedicated libraries that explicitly mark the data as pure content that should never get executed.

Validate incoming JSON schemas

Validate the incoming requests’ body payload and ensure it qualifies the expectations, fail fast if it doesn’t. To avoid tedious validation coding within each route you may use lightweight JSON-based validation schemas such as jsonschema .

Limit the allowed login requests of each user

A brute force protection middleware such as express-brute should be used inside an express application to prevent brute force/dictionary attacks on sensitive routes such as /login based on request properties such as the username.

Hide error details from clients

An integrated express error handler hides the error details by default. However, they are chances that you implement your own error handling logic with custom Error objects. If you do so, ensure not to return the entire Error object to the client, which might contain some sensitive application details.

Limit payload size

The bigger the body payload is, the harder your single thread works in processing it. This is an opportunity for attackers to bring servers to their knees without tremendous amount of requests. Mitigate this limiting the body size of incoming requests on the edge or by configuring express-body-parser to accept only small-size payloads.