20 Hacks to Secure Your Node.js Web Application

Kyle Gawley
Kyle Gawley
Gravity founder
20 Hacks to Secure Your Node.js Web Application

Protecting your web application against malicious attackers can feel like a full-time job that requires a PhD in cyber security, but it doesn't have to be complicated.

Here are twenty security techniques that you can use with no effort, included in Gravity SaaS boilerplate, or add them to your existing application without any headaches.

1. Keep Packages Up-to-Date & Audit Regularly

The incredible super-power of every Javascript developer is the seemingly endless supply of third-party packages that you can plug into your application.

While this provides time-saving benefits, it also means that the majority of your application code may be written by other developers and could contain a myriad of security vulnerabilities.

You can reduce the risk by making sure you're using the latest packages.

Install the free ncu package to check which packages are out of date and automatically update them.

It would be best if you also ran:

npm audit	

regularly to detect any security issues in third-party packages that need to be patched.

2. Use UUID Instead of Auto Increment

Avoid using ID columns with incremental counts like 1,2,3 - an attacker could easily guess and inject these IDs. Instead, use a UUID which will be close to impossible to guess.

3. Secure Your Database Queries with User IDs

One poorly written database query can easily expose or delete sensitive customer data. Every table in your database should have a user_id column with a foreign key relationship that ties that row to a specific user.

Then, when you're writing a query, append:

where user_id = 'f2d80113-9de2-485d-b062-0393db0ab857' 

This ensures you only ever select rows that belong to the current user. You can also achieve this by joining multiple tables, but using one table will improve performance.

4. Escape MySQL Queries to Prevent SQL Injections

Never inject variables directly into a SQL query – an attacker can exploit this to extract or manipulate data in your table. Always escape user input.

A query-builder like Knex automatically escapes your queries for you so you don't need to worry about it.

5. Cookies or Local Storage? Prevent XSS Attacks Instead

There is a lot of debate over whether you should use cookies or local storage for storing sensitive information like user tokens. Both have their own vulnerabilities, so the best course of action is to focus on protecting your application from cross-site-scripting (XSS) attacks.

A XSS attack would allow an attacker to inject code into the user's browser that extracts sensitive information like a JWT from their local storage and sends it to a remote server.

Popular frameworks like React will protect your application from XSS attacks by escaping data before writing it to the DOM, providing you don't use dangerouslySetInnerHTML.

If you are using cookies, be sure to set the domain and httpOnly flag and protect against CSRF attacks.

6. Use a Content Security Policy

Adding a CSP helps protect your application from XSS attacks that allow an attacker to inject foreign scripts into your client-side code.

Add Helmet to your Express server. You'll need to configure your policy to allow any scripts that you currently load remotely, like Stripe or Google fonts.

7. Use Rate Limiting to Protect Against Brute Force Attacks

Throttling your API endpoints can help with preventing spam signups to blocking multiple sign-in attempts. Use express-rate-limit to do the heavy lifting for you.

8. Flag Suspicious Sign-in Attempts

Help users keep their accounts secure by notifying them of suspicious sign-in attempts.

Gravity handles this by logging the browser, device and IP address of every login attempt. If one of these three items differs from previous attempts, the user is notified immediately by email.

If all three variables are different from previous logins, the sign-in attempt is blocked, and the user is emailed a link to sign in.

9. Don't Encrypt Passwords

A shocking 30% of websites store passwords in plain text. Equally as bad, many applications use two-way encryption to store passwords. If an attacker got their hands on the encryption key, these passwords could be exposed.

Instead, use a library like bcrypt to salt and hash passwords.

10. Enforce Secure Passwords

Did you know a six-digit password takes four months to crack while eight digits can take up to ten years?

The weakest link in your application may be your users - enforce password complexity rules like a minimum length or the requirement for special characters.

11. Avoid Passwords Entirely

An alternative to password security is not to use passwords at all. You could allow users to sign in using their social media accounts and let Facebook or Twitter handle security for you.

In addition to passwords, Gravity supports magic sign-in links so users can request a magic link via email, and sign in securely without a password.

12. Use ENV Files

Avoid storing sensitive application information like API keys and secrets in config files and never hard code these values. Always use environment variables. If an attacker gets access to your code, they won't be able to see these files.

It's very easy to import config files and accidentally send sensitive data to the client; this could easily expose database credentials or other sensitive information.

13. Use Secure Token-Based Authentication

Use oAuth or JWT for secure authentication in your application. After a user has authenticated by username and password, store a token in their browser and pass it to your server with every request.

The token should contain their user ID and permission level, but never sensitive information like their email or password.

When a user makes a request, decode the token, extract the user_id and use this to make queries. Combine this with Hack #2 above for extra database security.

14. Encrypt at the Application Layer

If you need to encrypt sensitive information in your database, it's best to handle the encryption in your application and then store it.

Database encryption functions like AES_DECRYPT in MySQL will decrypt your data first and then send it to your application. An attacker could intercept the connection between your application and database and view the unencrypted data.

Node.js comes with a built-in crypto library, or use cryptr if you want a more straightforward option.

15. Write Your Own Error Messages

When an error occurs on your server, you should avoid passing any information to the client that might expose sensitive architectural design information. It's both good security and good design practice, as it will force you to write user-friendly error messages instead of passing the default exception to the user.

16. Use Two-Factor Authentication

Similar to the magic link hack in #11, allow users to verify their identity with a secondary device. This can easily be set up to work with SMS using a service like Twilio.

Equally, you should use 2FA on any services that your application relies on, such as Stripe or AWS.

17. Use Access-Control on Endpoints & Routes

Ensure all private routes and API endpoints in your application have access controls on them to prevent unauthorised access. You should routinely test these with integration tests and deliberately exclude the access token, sometimes merely forgetting to include an access check can give a user access to something they shouldn't see.

18. Never Trust User Input

Always sanitise user input before storing it in your database. Check for and strip malicious content like <script> tags that could later be injected into your front-end application. You should also avoid using Javascript functions like eval or dangerouslySetInnerHTML in React.

19. Never Trust The Host Header

If your application needs to send links that contain a dynamic link to your domain, never use the host header to determine the domain.

For example, when sending a password reset link, always use a hard-coded domain in a config file or env var. Relying on a dynamic domain such as that stored in the host header could allow an attacker to spoof the header and trick your application into routing an email with a password reset token to them instead.

20. Encrypt ALL Traffic

It's 2020, and there's no excuse for running a web application over HTTP. You should be using HTTPS for everything; this is now incredibly simple to set up and free with most services.

If you'd like to learn more about web application security, check out the OWASP Foundation. They have a great checklist you can use, and even an unsecured web app called Juice Shop that you can use to practice your penetration testing skills.

The folks at PortSwigger (the makers of BurpSuite) have a free web app hacking course if you'd like to learn some new skills in this area.

If this all sounds like too much trouble, the Gravity SaaS boilerplate comes with all of these features included for you.

Download The Free SaaS Boilerplate

Build a full-stack web application with React, Tailwind and Node.js.