Everyone agrees that web application security is very important but few take it seriously. Here’s a 13-step security checklist that you should follow before deploying your next web application.
I have added links to some npm modules which assist in solving some of these problems, where appropriate.
1. Use SSL for communication
If there’s one thing that your web application should do, it’s this. Ensure that all communication with your server goes through SSL. This ensures that communication between a user and your server is encrypted.
Normally, if you are using nginx or some load balancer, SSL will be terminated before it hits your application. However, if you’re just using an Express server, it can still support SSL. Check out this StackOverflow response, which links to the relevant documentation on how to set up SSL on your Express server.
You can get an SSL certificate for free from StartSSLLetsEncrypt, so there’s no reason for you to not have an SSL Cert. (ipgof notes that StartSSL certs have started to be distrusted by Mozilla).
Yes, I’m aware my blog doesn’t have SSL and I’m working on it.
2. Always escape user data
Ensure that data that users input through forms is escaped. There are different ways of doing this. A common approach is to use a sanitizer like Validator.js, which ensures that your forms submit correct datatypes.
On the server-side, you should never directly input data into a raw database query. More on this below.
3. Use prepared statements for database queries
Creating database requests where you directly pass user information into a statement is a recipe for disaster. Instead, consider using prepared statements.
A prepared statement is a template created by the application and sent to the database. Certain values are left unspecified.
Prepared statements are resilient against SQL injection, because parameter values, which are transmitted later using a different protocol, need not be correctly escaped. If the original statement template is not derived from external input, SQL injection cannot occur.
If you’re using an ORM to access the database (Mongoose, Sequelize, Waterline, etc), the ORM will normally take care of SQL injection by using prepared statements under the hood. Check your ORM’s documentation to see if they do this.
4. Remove sensitive information from Request URLs
When building web applications, we normally follow RESTful conventions when displaying data to the user. For example, you may have a user profile page, and the URL may be something like this:
In this case, we are publicly displaying the userId to the end user. While that may be fine, there may also be reason to hide these IDs. Similarly, you can imagine scenarios where public URLs disclose sensitive information.
There’s no way to check for this programatically, but you should go through your routes definitions to double-check that your routes and query strings do not display sensitive information.
Remember to sign up to my newsletter if you want to read more articles like this.
5. Allow redirects only to whitelisted or hardcoded URLs
Do not have a server-side redirect that redirects somewhere based on user input. Instead, every redirect should be to a hardcoded URL. This makes it easy to test and ensure that redirects go where you expect them to.
6. Disable all unused API routes
Before deploying your web application to production, ensure that all your API routes are being used, and disable any that are unused or unprotected. This is especially important if you are using a library that automatically generates REST API endpoints, like Sails or Feathers.
7. CSRF Token should be present in all pages that create or update data
Cross-Site Request Forgery (CSRF) is an attack that forces a user to execute unwanted actions on a web application in which they’re currently logged in.
Ensure that all your pages have CSRF protection, or atleast all pages that create or update data (pages that call non-GET API endpoints).
In Node.js, you can use the csurf module which provides CSRF middleware for Express applications.
If you’re using an MVC Framework like Bedrock or Sails, CSRF protection is usually built in. Read the project documentation to figure out how to enable it.
8. Provide log off or exit functionality which expires session
Simple, but often overlooked. Ensure there is a way for users to log off your site and expire any sessions. People could be using your web application from public computers. If you’re using a user authentication system such as PassportJS, this is often trivial:
req.logout();//provided by passport
Importantly, test this to make sure sessions and cookies are being removed as expected.
9. HTTP Only Cookie Attribute for all pages and links
Implementing this in a Node application should be pretty easy. Just update your session configuration:
This prevents the browser from automatically setting certain attributes. You shouldn’t set this for every input element as it does harm the user experience. Just be judicious and think through the use cases.
11. Set your X-Frame-Options set to DENY, SAMEORIGIN, or ALLOW-FROM
The X-Frame-Options HTTP response header can be used to indicate whether or not a browser should be allowed to render a page in a <frame> or <iframe>. Sites can use this to avoid Clickjacking attacks, by ensuring that their content is not embedded into other sites. Set the X-Frame-Options header for all responses containing HTML content. The possible values are “DENY”, “SAMEORIGIN”, or “ALLOW-FROM <url>”.
Generally, unless you have a good reason to allow your web application to be viewed via an iframe, it’s best to set X-Frame-Options: DENY.
In a Node Application, you can do this using a module like Helmet, which provides multiple security HTTP headers, including this fix.
12. Set Security HTTP Headers
Following on from the previous point, there are a few other HTTP headers which you should set for your application.
Helmet can help with all of these, so I recommend that.
13. Protect from Brute Force and DDOS Attacks
To prevent your site from being bombarded by a large set of requests and subsequently crashing, you should build in some type of rate limiting to all your requests.
If you are building a Node application with Express, you can use the express-rate-limit middleware. The ratelimiter npm module is also good, but it has a Redis dependency.
app.enable('trust proxy');// only if you're behind a reverse proxy (Heroku, Bluemix, AWS if you use an ELB, custom Nginx setup, etc)
windowMs:15*60*1000,// 15 minutes
max:100,// limit each IP to 100 requests per windowMs
delayMs:0// disable delaying - full speed until the max limit is reached