How to safely use dangerouslySetInnerHTML in React
How to safely use dangerouslySetInnerHTML in React

How to safely use dangerouslySetInnerHTML in React

Dead Simple Chat Team

Dead Simple Chat allows you to add Chat to your application using its powerful Chat API and SDK. You can add chat to any React or web application in minutes with Dead Simple Chat.

As the name suggests dangerouslySetInnerHTML should be used cautiously. It is like the innerHTML property that is exposed by the DOM node.

With dangerouslySetInnerHTML you can set the HTML of the element. React does not perform any sanitization on the HTML set using dangerouslySetInnerHTML

It is called dangerouslySetInnerHTML because it is dangerous if the HTML that is set is unfiltered or unsanitized because it exposes the risk of injecting malicious code, XSS attack and other security threats that could compromise the application.

Hence dangerouslySetInnerHTML should be avoided unless absolutely necessary and before dangerouslySetInnerHTML, the HTML input should be sanitized.

In this blog post, we will look at some examples of how to use dangerouslySetInnerHTML and how to safely sanitize the HTML before setting using dangerouslySetInnerHTML.

Basic Usage Example

Here is the basic usage example of dangerouslySetInnerHTML

import React from "react";

export default function App() {
  const htmlContent = "<p>This is raw <strong>HTML</strong> content.<p>";

  return (
    <div className="App">
      <h1>Raw HTML</h1>
      <div dangerouslySetInnerHTML={{ __html: htmlContent }}></div>
    </div>
  );
}

In the above example, we set the raw html string stored in the variable htmlContent

The htmlContent will be set as innerHTML of the <div></div> tag. We pass dangerouslySetInnerHTML prop an object with the key __html and the value should contain the HTML string that we want to set.

The HTML should be sanitized before being used with dangerouslySetInnerHTML as it exposes security risks. In the above example the HTML is not sanitization and doing this not recommend as it is a bad practice and would result in sever security risks.

In the next section, we will see how to sanitize the HTML string before setting it as a value using dangerouslySetInnerHTML.

Output of our Application showing Raw HTML rendered

Sanitizing with DOMPurify

In the previous section, we specified the HTML string and setting it directly to dangerouslySetInnerHTML which is not a good practice.

In this section, we will use the package DOMPurify to santize our HTML string before using it in  dangerouslySetInnerHTML.

Let's first install the DOMPurify package using npm install

npm install dompurify

Then we will update our component to use DOMPurify:

import React from "react";
import DOMPurify from "dompurify";

export default function App() {
  const htmlContent = "<p>This is raw <strong>HTML</strong> content.<p>";
  const sanitizedHtmlContent = DOMPurify.sanitize(htmlContent);
  return (
    <div className="App">
      <h1>Raw HTML</h1>
      <div dangerouslySetInnerHTML={{ __html: sanitizedHtmlContent }}></div>
    </div>
  );
}

Using the DOMPurify library is very easy, we just need to call the santize method on the DOMPurify library and it returns the sanitized version of the HTML.

We can then pass the sanitized version to dangerouslySetInnerHTML prop.

Verifying HTML Sanitization

To check if our DOMPurify, we will inject an XSS payload into our HTML string and see if our DOMPurify correctly escapes the HTML script.

import React from "react";
import DOMPurify from "dompurify";

export default function App() {
  const htmlContent = "<script>alert(1);</script>";
  const sanitizedHtmlContent = DOMPurify.sanitize(htmlContent);
  return (
    <div className="App">
      <h1>Raw HTML</h1>
      <div dangerouslySetInnerHTML={{ __html: sanitizedHtmlContent }}></div>
    </div>
  );
}

We have updated our htmlContent to <script>alert(1);</script> and if our HTML is not sanitized correctly then the page will display an alert with 1.

Apart from DOMPurify there are other sanitization libraries available, that you can use like sanitize-HTML. But when choosing a library make sure you use a library that is actively developed, has a large user base and is widely used.

Contextual escaping: Understanding when and how to use it.

Contextual escaping also called output escaping is a security technique that is used to prevent Cross site scripting attacks.

It ensures that the user generated content is safe to render in the browser. It works by using a escaping strategy that escapes certain words and char before they are rendered on the browser.

This is different than user input sanitation which sanitises the before it is being stored on the server. the contextual escaping santises data before it is being given to the browser to render

When can you use contextual escaping

  • Displaying user generated context
  • working with rich text
  • Interpolating variable in JS
  • Including data in the URL

Implementing Content Security Policy (CSP) as an additional layer of protection.

Content security policy is a security standard that allows developers to declare which dynamic resources are allowed to load, thus making it difficult for an attacker to load harmful content

This content security policy can be used to avoid a wide range of attacks such as cross site scripting and injection attacks

How to implement Content security policy

  • Define the policy

Define which sources are trustworthy and can be allowed to provide scripts, styles, images.

One type of policy would be to restrict the user generated scripts.

That is only the scripts that are hosted on your servers would be allowed to run in the browser

  • Using the Content-Security-policy HTTP header:

To implement your content security policy you need to implement the Content-Security-Policy header with the policy definations in your responses

  • Testing the policy: You can use various online tools such as the google CSP evaluator to check your policy and test out whether there are any loopholes in it

Best Practices or tips for creating your Content-Security-Policy

  • Start with a strict policy and then relax as need:

To make things easier to implement, start with a strict policy, then when you see what stuff is breaking incrementally allow easier choices

  • Regularly review and update the policy:

We should at regular intervals review and update the policy because as the apps get more sofisticated because of added features , more and more resources need to be whitelisted in the security policy and some resources need to be removed as well.

  • Use Nonce or hashes for inline scripts:

If you are using inline scripts or css then use nonce or hashes for these.

Alternatives to Consider before using dangerouslySetInnerHTML

dangerouslySetInnerHTML should be used only when it is absolutely necessary and should be avoided whenever possible due to the security risks.

Always other options should be considered before using dangerouslySetInnerHTML, and here are some of the options that you should consider, but make sure to santize your HTML using a library like DOMPurify first:

  1. Try to use JSX First: You should first try to use JSX, if you have legacy code that you want to integrate, or you are integrating some 3rd party library try to use it JSX and with refs and only use dangerouslySetInnerHTML as the last resort.
  2. Use Library that converts HTML to JSX: There are multiple libraries avalaible which parses HTML into JSX, you can try to use those libraries as well, some of the popular options include:
  • html-react-parser: It allows you to parse raw HTML and convert it into React elements. It is a safer alternative to dangerouslySetInnerHTML. However you still need to sanitize the HTML. As of writing this library has 1.6K stars on Github and 990,820 weekly downloads on npm
  • react-html-parser: This also allows you to convert raw HTML into react components, and it is similar to html-react-parser. It has 742 starts on github as of writing and 277k weekly download on NPM. Also thing to note that it was last updated in 2020.

Other alternatives to using dangerouslySetInnerHTML

React Portals:

React portals are a way to render children into a DOM nodes that exists outside the DOM hierarchy of the parent component.

They are not a direct alternative to directly adding HTML with dangerouslySetInnerHTML portals can be a solution for adding  components or content outside the regular component tree

server side rendering

Before using these libraries make sure to sanitize the HTML using HTML sanitization libraries like DOMPurify.

  • Sanitize Content on the server:

When using server side rendering always sanitize the user generated content before it is saved and when it is sent to the client browser you only get the safe content.

  • Caching Sanitized Content:

You can also cache the sanitized content to improve performance. This also reduces the need to re sanitize the content and saves resouces that are spent in sanitizing the HTML content

  • Hydration considerations

When you are using the Server Side rendering the HTML generated on the server is hydrated in a fully reactive application on the client side

You need to ensure that the dynamic content that is rendered exactly as we want it not and does not result in a mismatch between server rendered markup and what is rendered on the client side

Scenarios where dangerouslySetInnerHTML could be used

Sometimes it is inevitable to use dangerouslSetInnerHTML and there are cases where you cannot get away without using dangerouslySetInnerHTML, let. discuss some of those scenarios:

  1. HTML data coming from a Trusted Source: When your HTML content is coming from a Trusted Source like your Content Management System or from the Server Generated content. In these cases, you can use the dangerouslSetInnerHTML. But make sure that you trust the source of the data.
  2. Properly Sanitized Content: You can safely use dangerouslySetInnerHTML content that is properly sanitized. Make sure you use a robust and well-tested sanitization library to escape any unsafe tags and XSS code.
  3. When Integrating 3rd Party Libraries: Some 3rd Party libraries do not integrate well in React, in those cases you have to use dangerouslySetInnerHTML to integrate the library with your code. But before doing that make sure you trust the library and is well-vetted and does not expose your application to any security risk, and generate content is well-sanitized.

Building a Markdown Editor in React using ShowDown, DOMPurify and dangerouslySetInnerHTML

Let's build a Markdown editor, that displays that Markdown output in HTML in real-time.

To build this application we will build a component that would take markdown and convert it into HTML.

We will also use the library will work in the following manner:

  1. Build a React Component Accept Markdown text as a prop
  2. Use ShowDown to convert Markdown to HTML
  3. Use DOMPurify to sanitize the rendered HTML

Building MarkDownViewer

We will create a MarkdownViewer.js component, which will accept markdown as a prop and convert it into HTML and display it on the screen.

Create a file called as src/MarkdownViewer.js and add the following code:

import showdown from "showdown";
import DOMPurify from "dompurify";
import React from "react";

function MarkdownViewer({ md, styles, className }) {
  const converter = new showdown.Converter();
  const html = converter.makeHtml(md);
  const sanitizedHTML = DOMPurify.sanitize(html);

  return (
    <div
      styles={styles}
      className={className}
      dangerouslySetInnerHTML={{ __html: sanitizedHTML }}
    ></div>
  );
}

export default MarkdownViewer;

In the above code, we have created a MarkdownViewer component, we have first imported the dependencies showdown and dompurify.

You can install them using npm

npm install showdown
npm install dompurify

Then we are creating a converter object and converting markdown to html.

  const html = converter.makeHtml(md);

Then we are sanitizing the generated HTML using the DOMPurify library:

const sanitizedHTML = DOMPurify.sanitize(html);

Finally, we set the generated HTML as dangerouslySetInnerHTML to the div tag

  return (
    <div
      styles={styles}
      className={className}
      dangerouslySetInnerHTML={{ __html: sanitizedHTML }}
    ></div>
  );

Building the Editor

The Editor Component is very simple, it will contain a textarea and will accept onChange method as a prop.

Create a file called as src/Editor.js to hold our Editor Component.

We will call the onChange method from the prop when the value of the textarea changes.

export default function Editor({ onChange, styles, className }) {
  return (
    <textarea
      styles={styles}
      className={className}
      onChange={onChange}
    ></textarea>
  );
}

Putting it all together

Now, let's open our src/App.js file and import the MarkdownViewer and Editor components.

We will attach the onChange listener to the Editor component, get the value from the Editor and set it as a prop to the MarkdownViewer component to display the Markdown typed by the user.

import React, { useState } from "react";
import Editor from "./Editor";
import MarkDownViewer from "./MarkdownViewer";
import "./styles.css";

export default function App() {
  const [editorValue, setEditorValue] = useState("");
  function handleOnChange(event) {
    setEditorValue(event.target.value);
  }

  return (
    <div className={"container"}>
      <Editor className={"half"} onChange={handleOnChange} />
      <MarkDownViewer className={"half"} md={editorValue} />
    </div>
  );
}

In the above cover, we have created a state variable called as editorValue and created a method called as handleOnChange.

We are passing the handleOnChange method as a prop to the Editor component, when the value changes in the Editor Component, we are updating the editorValue when textarea changes.

Then we are passing editorValue to the Markdown viewer component.

Demo

Here is the Demo of our Markdown Editor

0:00
/

Performance impact of using

Metered TURN servers

  1. Global Geo-Location targeting: Automatically directs traffic to the nearest servers, for lowest possible latency and highest quality performance. less than 50 ms latency anywhere around the world
  2. Servers in 12 Regions of the world: Toronto, Miami, San Francisco, Amsterdam, London, Frankfurt, Bangalore, Singapore,Sydney (Coming Soon: South Korea, Japan and Oman)
  3. Low Latency: less than 50 ms latency, anywhere across the world.
  4. Cost-Effective: pay-as-you-go pricing with bandwidth and volume discounts available.
  5. Easy Administration: Get usage logs, emails when accounts reach threshold limits, billing records and email and phone support.
  6. Standards Compliant: Conforms to RFCs 5389, 5769, 5780, 5766, 6062, 6156, 5245, 5768, 6336, 6544, 5928 over UDP, TCP, TLS, and DTLS.
  7. Multi‑Tenancy: Create multiple credentials and separate the usage by customer, or different apps. Get Usage logs, billing records and threshold alerts.
  8. Enterprise Reliability: 99.999% Uptime with SLA.
  9. Enterprise Scale: With no limit on concurrent traffic or total traffic. Metered TURN Servers provide Enterprise Scalability
  10. 50 GB/mo Free: Get 50 GB every month free TURN server usage with the Free Plan
  11. Runs on port 80 and 443
  12. Support TURNS + SSL to allow connections through deep packet inspection firewalls.
  13. Support STUN
  14. Supports both TCP and UDP

You might be interested in some of our other articles

Conclusion

In this blog post, we have learned what dangerouslySetInnerHTML is, and how to safely use it in our application.

We have also looked at ways to avoid using dangerouslySetInnerHTML and its alternatives and also looked at ways to safely escape the HTML code before rendering it using dangerouslySetInnerHTML.