Preventing XSS in React Web Applications

React is designed with mechanisms to prevent cross-site scripting (XSS). However, like any front-end framework, it can still be vulnerable if used incorrectly. Understanding React’s built-in protections and the scenarios in which they can fail is important for website security.

Understanding XSS and its impact on web applications

What is cross-site scripting (XSS)?

XSS is a vulnerability that allows attackers to inject malicious scripts into web pages that other users view. These scripts run in the target browser and can steal sensitive data, impersonate users, or manipulate the behavior of websites. It remains one of the most common vulnerabilities in web applications, frequently appearing in the OWASP Top 10.

Types of XSS

  • Reflected XSS occurs when malicious input is sent to the browser and appears in the HTTP response, for example, via URL parameters.
  • Stored XSS involves sending malicious scripts to the backend (such as in a database) that are later exposed to users through the normal operation of the website.
  • DOM-based XSS exploits vulnerabilities in client-side scripts that dynamically modify the DOM based on user input without sanitization.

How React helps prevent XSS

The React framework (originally React.js) was one of the first to be built with security in mind and includes features that make it difficult to implement XSS.

One of the main innovations of React is JSX, a syntax extension that allows developers to write HTML-like elements directly in JavaScript. JSX is compiled to JavaScript and rendered using React’s virtual DOM, which adds a layer of abstraction and security when interacting with the DOM.

Automatic escaping in JSX

React helps protect against XSS by primarily escaping values displayed in JSX before inserting them into the DOM.

How React renders HTML

By default, React escapes all values embedded in JSX expressions before rendering them into the DOM. This is done using a mechanism that ensures that the input is treated as plain text and not interpreted as executable HTML or JavaScript. This greatly reduces the likelihood of XSS.

What characters are escaped and why

Characters such as <, >, &,, and are escaped into their corresponding HTML entities (e.g. &lt;, &gt;). This prevents them from being interpreted as HTML tags or attributes, which could otherwise lead to code injection.

Virtual DOM and safe rendering

React’s Virtual DOM adds another layer of protection by controlling how updates are applied to the real DOM. Instead of directly manipulating the DOM based on strings or templates, React creates a virtual representation that is differentiated and sanitized before updates are made. This minimizes the risk of unexpected script execution.

Handling dynamic content in React

Dynamic content from user input or APIs can be safely rendered using JSX, as React escapes it by default. However, if developers circumvent these mechanisms, for example by manually entering HTML, XSS risks re-emerge appear. Secure rendering requires avoiding raw HTML and using reliable sanitization libraries when necessary.

What can make React websites vulnerable to XSS?

Despite its security settings, React is not immune to XSS vulnerabilities. Developers can still create risks through certain APIs or misuse of functions that bypass normal security mechanisms.

JSX itself is safe when used correctly, but it is not a sandbox—it will reproduce anything it is told to do, which still makes secure coding practices important.

Using dangerouslySetInnerHTML

One of the most common ways to reintroduce XSS into a React website is to use dangerouslySetInnerHTML, which bypasses React’s built-in protection.

Why it exists

The dangerouslySetInnerHTML attribute exists to support scenarios where raw HTML needs to be exposed, such as displaying content from a CMS or HTML-formatted Markdown. Its use is explicitly marked as “dangerous” to prevent misuse and to highlight security risks.

How it can be abused

If developers use dangerouslySetInnerHTML to display content containing user input or untrusted data, attackers can inject scripts that will be executed in the users’ browser. This completely bypasses React’s protection mechanisms and creates a direct path for XSS.

Incorrect use of eval(), innerHTML, or document.write()

Functions like eval(), or properties like innerHTML, are inherently dangerous because they can execute arbitrary JavaScript. Using them on untrusted data creates XSS vulnerabilities because any malicious script passed will be executed with privileges in the context of the page.

Giving React web applications access to untrusted third-party scripts

Loading external scripts into a React website without integrity checking or sandboxing poses risks. They can be compromised or behave maliciously, potentially accessing sensitive data or manipulating the state of the website.

Client-side routing issues with unescaped input

Client-side routing libraries often use dynamic segments from URLs to render content. If these segments are injected directly into the DOM without escaping or sanitizing, attackers can use them to execute scripts via DOM-based XSS.

Real-world examples of XSS in React web applications

Example 1: Using raw Markdown

For instance, there may be a component that renders Markdown using a parser that outputs raw HTML. If this output is injected with dangerouslySetInnerHTML without sanitization, attackers can exploit it.

function MarkdownViewer({ content }) {
  return (
    <div dangerouslySetInnerHTML={{ __html: marked(content) }} />
  );
}

// Malicious input: `![x](x "onerror='alert(1)')"`

Without sanitizing content, this approach can allow injection of <img> tags with onerror handlers that execute JavaScript.

Example 2: Injecting external data via API

There may be a web application that retrieves news from an external API and displays it using raw HTML.

useEffect(() => {
  fetch('/api/news')
    .then(res => res.text())
    .then(html => setContent(html));
}, []);

return <div dangerouslySetInnerHTML={{ __html: content }} />;

If the API is compromised or the data is not properly filtered, attackers can inject scripts via the returned HTML, exposing users to XSS.

Example 3: Misconfigured dangerouslySetInnerHTML

Even with good intentions, misconfigured use of dangerouslySetInnerHTML can lead to XSS.

const SafeHtml = ({ rawHtml }) => (
  <div dangerouslySetInnerHTML={{ __html: rawHtml }} />
);

// rawHtml might contain: `<script>alert('XSS')</script>`

If rawHtml is not sanitized before transmission, it will result in the execution of arbitrary scripts embedded in the input data.

Best practices for preventing XSS in React

Not using dangerouslySetInnerHTML unless absolutely necessary

When teams need to display raw HTML, it is a good idea to sanitize the content with a trusted library and validate the data source.

Verified libraries for HTML sanitization

Libraries like DOMPurify are specifically designed to sanitize HTML and remove potentially dangerous elements and attributes. They should always be used before injecting HTML into the DOM.

Backend input validation and sanitization

Server-side validation ensures that malicious data doesn’t enter the system, complementing client-side protection.

Avoiding inline JavaScript and inline event handlers

It is a good idea to stick to JSX event binding and avoid creating handlers based on user input, which can lead to code injection.

Strict Content Security Policy (CSP)

A strong CSP can block scripts from unauthorized sources and add a critical layer of protection. It is especially useful as a fallback option when other protections fail.

For example, setting Content-Security-Policy: default-src ‘self’ restricts scripts to those hosted on a specific domain.

Using Security Headers

As with any other web application, a React-based website should use security headers to minimize the risk of not only XSS but many other classes of attacks.

HTTPOnly and Secure Cookies

It is a good idea to mark authentication cookies as HttpOnly to prevent access from JavaScript, and as Secure, to ensure they are only transmitted over HTTPS. This protects session data even if other parts of the website are compromised.

Referrer-Policy and X-XSS-Protection Headers

Referrer-Policy should be used to restrict sensitive referrer data in requests. Although X-XSS-Protection is deprecated, it may still offer limited protection in older browsers.

Methods for testing React websites for XSS

Static code analysis

SAST, such as that in Mend.io, can detect common coding issues, including unsafe API usage. It can be implemented early in development and is able to scan the entire codebase, providing assurance of security even at the beginning of a project.

However, static analysis lacks the context of the application’s execution, so it misses related vulnerabilities.

Dynamic testing (e.g., using vulnerability scanners)

Dynamic Application Security Testing (DAST) tools, such as Invicti (based on Netsparker and Acunetix), interact with running applications to detect real, usable vulnerabilities.

Also, vulnerability confirmation in Invicti helps teams focus on fixing real security issues, minimizing retesting.

Best practice: combining methods

To ensure code security from the early stages of development, to be able to scan all your code and detect all available vulnerabilities during website execution, it is important to combine DAST and SAST methods.

This way, the team can take a comprehensive approach to security testing, without missing important steps.

To test the Invicti (DAST) and Mend.io (SAST, SCA, container security) tools for free, please provide your contact details in the form below.

Request for free Invicti/Mend.io Trial



    Subscribe to news