How to safely use dangerouslySetInnerHTML in React

alakkadshaw

alakkadshaw

Posted on January 8, 2024

How to safely use dangerouslySetInnerHTML in React

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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.

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 available 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.

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

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

  const html = converter.makeHtml(md);
Enter fullscreen mode Exit fullscreen mode

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

const sanitizedHTML = DOMPurify.sanitize(html);
Enter fullscreen mode Exit fullscreen mode

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

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

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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

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

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.

💖 💪 🙅 🚩
alakkadshaw
alakkadshaw

Posted on January 8, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related