How to Secure React Portals

React Portals allow developers to render components outside of their parent DOM hierarchy while keeping them within the React virtual tree. While highly useful for modals, tooltips, and dropdowns, portals can introduce unexpected security vulnerabilities if not properly managed. This article explains how to secure React Portals by addressing event bubbling risks, preventing Cross-Site Scripting (XSS), validating mounting targets, and securing React Context access.

Control Event Bubbling and Propagation

Even though a portal is rendered outside the parent component’s DOM node, it still behaves like a standard React child in the virtual DOM. This means events fired inside a portal will bubble up to ancestral React components, regardless of where the portal is physically located in the HTML document.

If a portal renders user-generated content or interactive elements, uncontrolled event bubbling can trigger unintended actions in parent components, such as form submissions, state changes, or authentication resets.

To secure your portal against unwanted event bubbling, stop the propagation of events that do not need to reach the parent tree:

const ModalPortal = ({ onClose, children }) => {
  return ReactDOM.createPortal(
    <div className="modal-backdrop" onClick={onClose}>
      {/* Stopping propagation prevents clicking the content from closing the modal or triggering parent handlers */}
      <div className="modal-content" onClick={(e) => e.stopPropagation()}>
        {children}
      </div>
    </div>,
    document.getElementById('portal-root')
  );
};

Prevent Cross-Site Scripting (XSS) in Portal Content

Portals are frequently used to display dynamic content, such as notifications, chat windows, or custom HTML previews. If the data rendered inside the portal is sourced from user input, it poses an XSS risk.

import DOMPurify from 'dompurify';

const SafePortalContent = ({ untrustedHTML }) => {
  const cleanHTML = DOMPurify.sanitize(untrustedHTML);
  
  return ReactDOM.createPortal(
    <div dangerouslySetInnerHTML={{ __html: cleanHTML }} />,
    document.getElementById('portal-root')
  );
};

Validate and Restrict the Target DOM Node

The second argument of ReactDOM.createPortal is the target DOM element where the portal will be mounted. Hardcoding or dynamically resolving this element without validation can expose your application to DOM manipulation attacks.

Secure Context and State Access

Because portals remain part of the React virtual tree, they retain access to any React Context provided by parent components. This includes sensitive contexts like authentication states, user permissions, and global store states.

If a portal renders untrusted third-party widgets or user-generated plugins, those components will have access to your application’s internal contexts.

Implement Content Security Policies (CSP)

Portals that inject elements dynamically can occasionally trigger Content Security Policy (CSP) violations, especially if they inject inline styles or scripts.

Ensure your application’s CSP headers allow for dynamic script execution only from trusted hashes or nonces, and enforce scoped CSS styles to prevent portal styles from leaking out and altering the host application’s UI (UI redressing/clickjacking protection).