How to safely use dangerouslySetInnerHTML in React
alakkadshaw
Posted on January 8, 2024
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
.
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.
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:
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.
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:
- 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.
- 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.
- 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:
- Build a React Component Accept Markdown text as a prop
- Use ShowDown to convert Markdown to HTML
- 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
Metered TURN servers
- 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
- Servers in 12 Regions of the world: Toronto, Miami, San Francisco, Amsterdam, London, Frankfurt, Bangalore, Singapore,Sydney (Coming Soon: South Korea, Japan and Oman)
- Low Latency: less than 50 ms latency, anywhere across the world.
- Cost-Effective: pay-as-you-go pricing with bandwidth and volume discounts available.
- Easy Administration: Get usage logs, emails when accounts reach threshold limits, billing records and email and phone support.
- Standards Compliant: Conforms to RFCs 5389, 5769, 5780, 5766, 6062, 6156, 5245, 5768, 6336, 6544, 5928 over UDP, TCP, TLS, and DTLS.
- Multi‑Tenancy: Create multiple credentials and separate the usage by customer, or different apps. Get Usage logs, billing records and threshold alerts.
- Enterprise Reliability: 99.999% Uptime with SLA.
- Enterprise Scale: With no limit on concurrent traffic or total traffic. Metered TURN Servers provide Enterprise Scalability
- 50 GB/mo Free: Get 50 GB every month free TURN server usage with the Free Plan
- Runs on port 80 and 443
- Support TURNS + SSL to allow connections through deep packet inspection firewalls.
- Support STUN
- 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
.
Posted on January 8, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.