Cross-site scripting (XSS) is a web security vulnerability that allows attackers to inject malicious content into a page viewed by other users. In a successful attack, a browser may execute attacker-controlled JavaScript in the context of a trusted website. Depending on the application’s permissions and security controls, this can expose sensitive page data, manipulate the interface, or allow unauthorized actions using the victim’s active session.
React helps prevent many common XSS vulnerabilities because it escapes text values rendered through its normal JSX mechanisms. However, React does not automatically make every operation involving HTML, URLs, third-party libraries, or direct DOM manipulation safe. Vulnerabilities can still appear when developers bypass standard rendering protections or treat untrusted input as executable content.
To prevent XSS attacks in React and JavaScript applications, use safe rendering by default, sanitize HTML when rich content is required, validate URLs, avoid dangerous DOM APIs, and deploy a carefully configured Content Security Policy (CSP). These measures should work together rather than relying on one security feature.
This guide explains the main XSS attack types, common mistakes, and practical defenses you can apply to a React application.
What Is Cross-Site Scripting (XSS)?
Cross-site scripting occurs when an application allows attacker-controlled content to be interpreted as executable code or otherwise dangerous browser content. The vulnerability commonly arises when untrusted data enters a page without appropriate encoding, sanitization, or validation for its destination.
According to the OWASP Cross Site Scripting Prevention Cheat Sheet, the correct defense depends on the context in which data is used. Text, HTML, URLs, JavaScript, and CSS do not all require the same protection.
The Three Common Types of XSS
Stored XSS: Malicious content is saved in a database or another persistent data store and later displayed to users. For example, an application that stores customer reviews could become vulnerable if it renders submitted HTML without appropriate sanitization.
Reflected XSS: Untrusted input from a request is immediately included in a response. Search pages, error messages, and query parameters can be affected if their values are inserted into HTML unsafely.
DOM-based XSS: Client-side JavaScript reads untrusted data, such as a URL fragment, and passes it to a dangerous DOM operation. The vulnerability can occur entirely in the browser without the server generating a malicious HTML response.
All three types require careful handling of untrusted data, but the exact remediation depends on where the data enters the application and how it is eventually rendered.
1. Use React’s Default Safe Rendering
React’s ordinary JSX rendering treats string values as text rather than interpreting them as HTML markup.
For example, consider a user profile displaying a submitted name:
function UserProfile({ name }) {
return <p>{name}</p>;
}
If name contains characters that resemble HTML tags, React renders them as text rather than executing them as markup. This is the preferred approach when the application only needs to display user-provided text.
Avoid building HTML strings from user input and injecting them directly into the page. Standard JSX rendering is safer and easier to maintain.
However, safe text rendering is not a replacement for all other security controls. You must still consider URL schemes, direct DOM manipulation, third-party components, and any code that deliberately interprets strings as HTML or JavaScript.
2. Avoid Unsafe Use of dangerouslySetInnerHTML
React provides dangerouslySetInnerHTML for situations where HTML markup must be inserted directly into the DOM.
For example:
function Article({ content }) {
return <div dangerouslySetInnerHTML={{ __html: content }} />;
}
This is dangerous when content contains untrusted HTML. The browser interprets the inserted markup, and malicious event handlers or other executable constructs may create an XSS vulnerability.
If your application only needs to display plain text, use normal JSX instead.
If you must render rich HTML, sanitize it with a maintained HTML sanitizer before passing it to dangerouslySetInnerHTML. Sanitization should follow a deliberate allowlist policy that permits only the tags, attributes, and URL schemes your application actually needs.
Do not assume that content is safe because it came from your own database. Stored data may originally have been submitted by users, imported from external systems, or modified through compromised accounts.
3. Sanitize User-Generated HTML With DOMPurify
Applications such as content editors, discussion forums, and blog platforms sometimes need to display user-authored formatting. In those cases, removing all HTML may not be practical.
DOMPurify is a widely used HTML sanitization library designed to remove dangerous markup while retaining permitted HTML.
Install it using npm:
npm install dompurify
Then sanitize content before rendering it:
import DOMPurify from 'dompurify';
function RichText({ html }) {
const cleanHTML = DOMPurify.sanitize(html);
return (
);
}
This example demonstrates the basic approach for browser-side rendering. For server-side rendering, ensure the sanitizer is configured with a compatible DOM implementation and maintained dependencies.
Configure Sanitization for Your Actual Requirements
Do not allow every HTML element and attribute simply because the sanitizer supports them. If your application only needs paragraphs, headings, lists, and links, restrict the permitted markup accordingly.
Sanitization is also not interchangeable with input validation. Validation checks whether data meets application rules, while sanitization removes or neutralizes unsafe content for a particular use.
The safe approach depends on the destination. HTML that is safe to display inside a content container is not automatically safe to place inside JavaScript, CSS, or an arbitrary URL.
Let’s Figure It Out.
Tell us what you’re working on and we’ll help you identify the right next step.
Talk to an Expert4. Validate URLs Before Rendering Links
User-submitted links introduce a separate security concern. An application may safely render a string as text while still creating a dangerous link if it accepts an inappropriate URL scheme.
For example, a profile editor should not blindly trust every value supplied for a link destination.
Use URL parsing and an explicit protocol allowlist:
function getSafeURL(value) {
try {
const url = new URL(value);
if (!['https:', 'http:'].includes(url.protocol)) {
return null;
}
return url.href;
} catch {
return null;
}
}
function ExternalLink({ href, children }) {
const safeHref = getSafeURL(href);
if (!safeHref) {
return {children};
}
return (
{children}
);
}
This example accepts absolute HTTP and HTTPS URLs. If your application needs relative URLs, handle those separately by resolving them against a trusted base URL and applying the same policy.
The rel attribute helps prevent the opened page from accessing the originating page through window.opener in relevant browsing scenarios. It does not replace URL validation.
Also validate URLs used in image sources, iframe sources, redirects, and other destinations according to their specific security requirements.
5. Avoid Dangerous JavaScript and DOM Operations
Some browser APIs interpret strings as code or HTML. These operations can create XSS vulnerabilities when their input comes from an untrusted source.
Review code that uses:
- innerHTML or outerHTML
- document.write()
- insertAdjacentHTML()
- eval()
- String-based setTimeout() or setInterval()
Prefer DOM methods that create elements and assign text safely:
const message = document.createElement(‘p’);
message.textContent = userInput;
document.body.appendChild(message);
The browser treats textContent as text rather than parsing it as HTML.
For delayed execution, pass a function instead of a string:
setTimeout(() => {
console.log(‘Task completed’);
}, 1000);
Search your codebase for dangerous APIs, especially in legacy components, third-party integrations, and code that processes URL parameters or API responses. Replace unsafe patterns where possible, and apply sanitization when actual HTML rendering is required.
6. Validate Input and Encode Output Correctly
Input validation and output encoding solve different problems, and a secure application often needs both.
Input validation checks whether submitted data meets expected rules. A phone number field, for example, should accept the formats your application supports rather than arbitrary content. Validation can reject invalid values, but it should not be treated as the only XSS defense.
Output encoding ensures that data is displayed as data rather than interpreted as executable markup in the destination context. React’s normal JSX text rendering handles this for ordinary text nodes. If you build HTML through server templates or another rendering system, use that system’s context-appropriate escaping mechanisms.
Do not create a universal function that merely removes angle brackets or replaces a few suspicious characters. Such filters are incomplete because browsers interpret HTML, JavaScript, URLs, and CSS according to different rules.
For further guidance, see OWASP’s Cross Site Scripting Prevention Cheat Sheet.
7. Add a Content Security Policy (CSP)
A Content Security Policy instructs the browser about which resources and scripts the page is allowed to load or execute. A properly configured policy can reduce the impact of an XSS vulnerability if unsafe content reaches the browser.
A basic policy might look like this:
Content-Security-Policy: default-src ‘self’; object-src ‘none’; base-uri ‘self’; frame-ancestors ‘self’
This is an illustrative starting point, not a complete policy for every application. A real React application may need carefully selected directives for scripts, styles, images, API connections, fonts, and embedded services.
For script execution, a strict CSP using nonces or hashes is generally preferable to broadly allowing inline scripts. Generate nonces securely for each response and apply them consistently to authorized scripts. Test the policy before enforcing it on a production site, especially if the application relies on third-party services.
CSP is a defense-in-depth measure, not a replacement for safe rendering and sanitization. See MDN’s Content Security Policy implementation guide.
8. Protect Authentication Data and Review Dependencies
An XSS vulnerability can allow malicious code to perform actions within the victim’s application context. Depending on the application and browser protections, it may access page data, interact with authenticated endpoints, or read sensitive information exposed to JavaScript.
For session cookies, use appropriate attributes such as HttpOnly, Secure, and SameSite where suitable. HttpOnly prevents JavaScript from reading the cookie directly, but it does not stop malicious scripts from making requests through the victim’s authenticated browser session.
Also review npm dependencies and third-party scripts. Outdated packages, compromised dependencies, and unsafe components can introduce vulnerabilities even when your own JSX appears secure.
Useful checks include:
- Run npm audit and review the reported vulnerabilities.
- Update affected packages to supported, patched versions.
- Review dependencies that manipulate HTML or the DOM.
- Remove unused packages and unnecessary third-party scripts.
- Use a lockfile and review dependency changes before deployment.
Security controls should be applied across the application, including the API, server-rendered pages, and client-side components.
9. Test Your React Application for XSS Vulnerabilities
Testing should focus on the complete path from untrusted input to its final use in the application.
Start with a code review. Search for dangerouslySetInnerHTML, innerHTML, document.write, direct URL assignment, and code that processes user-generated HTML. Check whether each use has a clear, appropriate security boundary.
Next, test input fields and content areas using harmless test strings that resemble HTML. Confirm that ordinary text fields display these strings as text and that rich-text fields remove markup that is not permitted by the sanitization policy.
Also inspect links, image URLs, API responses, and content loaded from third-party sources. A security scanner can help identify potential weaknesses, but automated tools may miss context-specific vulnerabilities. Manual review and tests remain important.
For more on browser-side risks, consult MDN’s Cross-Site Scripting guide.
Conclusion
Preventing XSS attacks in React and JavaScript applications requires more than relying on React’s default protections. Render ordinary values as text, sanitize HTML when rich formatting is necessary, validate URL destinations, avoid unsafe DOM APIs, and use CSP as an additional security layer.
Review authentication controls and dependencies, and test the complete path from user input to browser output. These practices help protect both users and application data while preserving the functionality your website needs.
8. Frequently Asked Questions (FAQs)
1. Does React automatically prevent XSS attacks?
React escapes string values rendered through ordinary JSX text expressions, which prevents many common injection attacks. However, it does not make every operation safe. Developers must take extra care with dangerouslySetInnerHTML, unsafe URLs, direct DOM manipulation, third-party libraries, and other operations that bypass normal text rendering.
2. How do I safely render HTML from users in React?
Use a maintained HTML sanitizer such as DOMPurify before rendering untrusted HTML through dangerouslySetInnerHTML. Define an allowlist of permitted elements, attributes, and URL schemes that matches your application’s requirements. If the content only needs to appear as plain text, render it through JSX instead of inserting HTML.
3. What is the difference between XSS sanitization and input validation?
Input validation checks whether data meets expected application rules. Sanitization removes or neutralizes unsafe content for a particular use, such as displaying rich HTML. Neither should be treated as a universal substitute for context-appropriate output encoding and safe rendering.
4. Can Content Security Policy completely prevent XSS?
No. A carefully configured CSP can block many unauthorized script execution paths and limit the impact of certain vulnerabilities, but it is not a complete substitute for safe coding practices. Combine CSP with proper output handling, HTML sanitization, URL validation, secure dependencies, and application testing.
5. Is dangerouslySetInnerHTML always unsafe in React?
No. It is a legitimate mechanism for rendering HTML, but using it with untrusted, unsanitized content can introduce XSS vulnerabilities. If your application needs rich HTML, sanitize the content using an appropriate maintained library and carefully control which elements, attributes, and URL schemes are permitted.
6. How can I check whether my JavaScript application has XSS vulnerabilities?
Start by reviewing code that handles untrusted input, especially dangerouslySetInnerHTML, innerHTML, URL parameters, and third-party content. Test whether submitted values are rendered safely, review dependencies, and use security scanners as an additional check. For applications handling sensitive data, include a dedicated security review and test both client-side and server-side rendering paths.
Let’s Talk About Your Business
Not sure which approach is right for your business? Get a free consultation with our team.
Get Free Consultation