How To

Hardening Your React Applications: A Practical Security Guide

By ScanLabs AI Security Team
October 7, 2026
10 min read
Back to Hub
Hardening Your React Applications: A Practical Security Guide — How To illustration | ScanLabs AI
Intelligence Brief

The modern web is increasingly powered by dynamic, interactive Single-Page Applications (SPAs), with React leading the charge for many businesses, from startups to enterprises. This technological shift brings incredible user experiences and development velocity, but it also introduces unique security considerations. As cyberattacks grow more sophisticated and data breaches continue to dominate headlines – a recent report highlighted that web application attacks are a leading cause of breaches – understanding and mitigating vulnerabilities in your React applications is no longer optional; it's a fundamental business imperative. Ignoring these risks can lead to financial losses, reputational damage, and a loss of customer trust. This guide provides a practical roadmap for securing your React applications effectively.

Guarding Against Cross-Site Scripting (XSS) in React

Cross-Site Scripting (XSS) remains one of the most prevalent and dangerous web vulnerabilities. It allows attackers to inject malicious client-side scripts into web pages viewed by other users. These scripts can then bypass access controls, steal sensitive data, deface websites, or even perform actions on behalf of the user. While React provides some built-in protections, it's not a silver bullet.

React's JSX by default escapes embedded values before rendering them to the DOM. This means that if you render a string like <h1>Hello <script>alert('XSS')</script></h1>, React will escape the < and > characters, rendering it harmlessly as plain text. This is a powerful first line of defense. However, XSS vulnerabilities can still arise from several scenarios:

  • Rendering unsanitized HTML: If your application needs to display HTML content directly, perhaps from a rich text editor or an external API, React's default escaping won't apply.
  • Dynamic attribute values: Setting attributes like href or src with unsanitized user input can lead to XSS. For example, javascript:alert('XSS') in an href attribute.
  • DOM manipulation outside React: If you directly manipulate the DOM using plain JavaScript without proper sanitization, you bypass React's protections entirely.

Actionable Steps for XSS Prevention:

  1. Always Sanitize User-Generated Content (UGC) on the Server: This is paramount. Never trust client-side input. Before storing any user-submitted data that might contain HTML or scripts, process it through a robust server-side sanitization library. For Node.js, DOMPurify (which works on the server too) or xss are good choices. Python has Bleach, and PHP has HTML Purifier. This ensures that even if a client-side vulnerability is found, your stored data is clean.
  2. Sanitize on the Client-Side Before Display (If Necessary): If you must render HTML from an untrusted source, even after server-side sanitization, consider a second layer of defense. DOMPurify is an excellent client-side HTML sanitizer. Pass the content through DOMPurify.sanitize() before assigning it to dangerouslySetInnerHTML (which we'll discuss next) or any other method that renders raw HTML.
    import DOMPurify from 'dompurify';
    
    function Comment({ commentHtml }) {
      const cleanHtml = DOMPurify.sanitize(commentHtml);
      return <div dangerouslySetInnerHTML={{ __html: cleanHtml }} />;
    }
    
  3. Validate and Whitelist Inputs: For dynamic attributes like href or src, ensure that the values conform to expected patterns (e.g., http:// or https:// for URLs). Avoid allowing arbitrary javascript: schemes.
  4. Avoid Direct DOM Manipulation: Stick to React's declarative rendering model. If you find yourself directly injecting HTML or JavaScript into the DOM using document.createElement or element.innerHTML, re-evaluate your approach.

Common Mistake: Believing React's default escaping is sufficient for all XSS scenarios. While it handles basic text rendering, complex cases involving raw HTML or dynamic attributes require explicit sanitization.

Taming dangerouslySetInnerHTML: A Necessary Evil?

React provides a special prop called dangerouslySetInnerHTML for components. As its name explicitly warns, using it is inherently dangerous. This prop allows you to programmatically set HTML directly into an element, effectively bypassing React's default escaping mechanisms. It's React's equivalent of innerHTML in vanilla JavaScript.

Why is it dangerous? If the HTML you're inserting contains untrusted or unsanitized content, especially user-generated content, you are opening a direct vector for XSS attacks. Malicious scripts embedded within that HTML will execute in the user's browser, as if they were part of your application's original code.

When is it necessary? There are legitimate use cases where you might need to render raw HTML:

  • Displaying content from a rich text editor (e.g., TinyMCE, Quill).
  • Rendering markdown that has been converted to HTML.
  • Integrating with third-party libraries that provide HTML snippets.

Actionable Steps for Safe Usage:

  1. Minimize Its Use: The golden rule is to avoid dangerouslySetInnerHTML unless absolutely necessary. Explore alternative approaches first. Can you achieve the desired layout using standard React components and styling?
  2. Source Must Be Trusted and Sanitized: This cannot be stressed enough. If you must use dangerouslySetInnerHTML, the HTML content you are passing to it must originate from a trusted source and must have been thoroughly sanitized both on the server-side and, if extra caution is warranted, on the client-side using a library like DOMPurify.
    import DOMPurify from 'dompurify';
    
    function RenderTrustedHTML({ htmlContent }) {
      // Ensure htmlContent is already sanitized on the server.
      // Add client-side sanitization as a safeguard.
      const safeHTML = DOMPurify.sanitize(htmlContent, { USE_PROFILES: { html: true } });
      return <div dangerouslySetInnerHTML={{ __html: safeHTML }} />;
    }
    
  3. Strict Content Security Policy (CSP): A robust CSP (discussed later) can act as a crucial layer of defense, even if an XSS payload somehow makes it through dangerouslySetInnerHTML. A well-configured CSP can prevent injected scripts from executing or communicating with external domains.

Common Mistake: Using dangerouslySetInnerHTML with content that hasn't been properly sanitized, or using it as a shortcut without understanding the profound security implications. Always question its necessity and ensure every possible precaution is taken.

Proactive Dependency Vulnerability Management

Modern React applications rely heavily on third-party libraries and packages from registries like npm. While this accelerates development, it also introduces a significant supply chain security risk. A vulnerability in just one of your thousands of direct or transitive dependencies can compromise your entire application. The average web application today pulls in hundreds, if not thousands, of external packages.

Actionable Steps for Dependency Security:

  1. Regularly Audit Dependencies:
    • npm audit / yarn audit: These built-in command-line tools scan your project for known vulnerabilities in your dependencies. Make running npm audit a standard part of your development workflow and CI/CD pipeline. Address critical and high-severity warnings promptly.
    • Automated Scanners: Integrate dedicated security tools like Snyk, Dependabot (for GitHub repositories), or SonarQube. These tools continuously monitor your project for new vulnerabilities, provide detailed reports, and often suggest remediation steps (e.g., upgrading to a patched version).
  2. Keep Dependencies Updated (Strategically):
    • Regularly update your dependencies to benefit from security patches and bug fixes. Use tools like npm-check-updates to identify outdated packages.
    • Don't blindly update. Test thoroughly after updates, especially for major version changes, as they might introduce breaking changes.
    • Consider using package-lock.json or yarn.lock to pin exact versions of your dependencies. This ensures consistent builds across environments, but requires a strategy for updating the lock file when security patches are released.
  3. Review New Dependencies: Before adding a new library to your project, take a moment to:
    • Check its popularity and maintenance status (e.g., number of stars, last commit date on GitHub).
    • Look for open security issues or past vulnerabilities.
    • Understand its functionality and ensure it's not overly broad for your needs.
    • Be wary of packages with very few downloads or unknown authors.
  4. Implement a CI/CD Security Gate: Configure your Continuous Integration/Continuous Deployment pipeline to fail builds if npm audit reports critical vulnerabilities or if your chosen security scanner flags high-severity issues. This prevents vulnerable code from reaching production.

Common Mistake: Ignoring npm audit warnings or neglecting dependency updates. Many breaches occur due to known vulnerabilities in outdated software components. It's not about if a dependency will have a vulnerability, but when, and how quickly you can address it.

Keeping Secrets Out of Frontend Code

A fundamental principle of application security is that anything sent to the client-side (the user's browser) should be considered public. This includes all your JavaScript, HTML, CSS, and any data bundled with your application. Consequently, you should never embed sensitive information – often referred to as "secrets" – directly into your React frontend code or its build artifacts.

Secrets include:

  • API keys for sensitive services (e.g., payment gateways, private backend APIs).
  • Database credentials.
  • Private cryptographic keys.
  • Authentication tokens that grant high privileges.

Why is this a problem? An attacker can easily inspect your application's source code, network requests, and local storage through browser developer tools. Any secret embedded directly in your JavaScript bundle will be immediately exposed.

Actionable Steps for Managing Secrets:

  1. Leverage Environment Variables (with caution): React applications built with Create React App (CRA) or similar build tools allow you to use environment variables (e.g., REACT_APP_API_KEY). These are injected into your build at compile time.
    • Crucial caveat: While they prevent secrets from being committed to source control, these variables are still embedded in the final JavaScript bundle. They are public and accessible to anyone who inspects your frontend code.
    • Use case: Environment variables are suitable for API keys that are meant to be public (e.g., Google Maps API keys that are restricted by domain or have public rate limits) or for configuration differences between development and production environments (e.g., API endpoint URLs).
    • Never for true secrets: Do not use environment variables for keys that grant access to sensitive data or perform critical operations without further authentication.
  2. Backend Proxy for Sensitive API Keys: For truly sensitive third-party API keys (e.g., payment gateway secret keys, private AI service keys), never expose them to the frontend. Instead, create a backend API endpoint that acts as a proxy. Your React app calls your backend, which then securely calls the third-party service using the secret key, and returns the result to your frontend.
  3. Secure Authentication and Authorization:
    • JWTs/OAuth: Implement a robust authentication system. When a user logs in, your backend should issue an access token (e.g., a JSON Web Token).
    • Access Token Storage: Store access tokens securely in memory for the duration of the session. Avoid localStorage for access tokens, as it's vulnerable to XSS. If XSS occurs, an attacker can steal tokens from localStorage.
    • Refresh Tokens (HTTP-Only, Secure Cookies): For longer-lived sessions, use refresh tokens stored in HTTP-only, secure, same-site cookies. HTTP-only prevents JavaScript from accessing the cookie, mitigating XSS risks. Secure ensures it's only sent over HTTPS. Same-site protects against CSRF. Your frontend can then use the refresh token (via the backend) to obtain new access tokens when the current one expires.
  4. Server-Side Rendering (SSR) Benefits: If using Next.js or similar SSR frameworks, you can fetch data and interact with secure APIs on the server before the page is sent to the client. This allows you to use true secrets

Check your own site

Reading about these risks is one thing; knowing whether your own website is exposed is another. Run a free security scan with ScanLabs AI to check your site for the issues covered here and get a clear, prioritised report of what to fix.


Source: the original report — this analysis is based on reporting from the original report.

Related reading

#how-to#cybersecurity#education#security-tips#online-safety#password-security#network-security#privacy

Related articles

ScanLabs AI Security Team

Researched and written by the ScanLabs AI Security Team — the researchers behind ScanLabs AI, an automated website security scanner that checks sites against thousands of known vulnerabilities and the OWASP Top 10. Our team tracks emerging threats daily to help businesses find and fix exposures before attackers do. Articles are AI-assisted and reviewed for technical accuracy.

Run a free security scan