React Security Checklist: 10 Vulnerabilities to Fix Before Launch

Quick answer: React apps are client-side, making every secret and route guard visible to users. To secure your app before launch, you must move API keys to the server, sanitize HTML with DOMPurify, validate user-provided URLs, and enforce backend authentication for every sensitive request rather than relying on frontend logic.

By Daniel A · Kraftwire Software

· 6 min read

To secure a React application before launch, you must move all secret API keys to a backend, sanitize user-provided HTML with DOMPurify to prevent XSS, validate all dynamic URLs, and enforce authentication on the server rather than relying on client-side route guards. React is a client-side library, meaning every line of code and bundled environment variable is public-facing; true security requires a "zero-trust" approach to the frontend.

Why React Security Requires a Different Mindset

React applications run entirely in the user's browser. This fundamental architecture means that your entire application logic, state management, and bundled configuration are visible to anyone with a web browser. Unlike traditional server-side applications where the code stays behind a firewall, a React bundle is an open book.

Many of these vulnerabilities stem from the "vibe-coding" habit of treating the frontend as a secure environment. When using tools like Windsurf or Cursor, it is easy to let the AI generate code that works perfectly but exposes sensitive data.

1. Eliminate Exposed Secrets and API Keys

The most common mistake in React development is including sensitive credentials in the frontend code. Whether you use VITE_ or REACT_APP_ prefixes, these variables are injected into the JavaScript bundle during the build process. They are not hidden; they are simply hardcoded.

What is Public

  • Stripe Publishable Keys
  • Firebase Configuration (API Key, Auth Domain)
  • Posthog or Mixpanel Project Tokens
  • API Base URLs

What Must Be Secret

  • Stripe Secret Keys
  • Database Connection Strings (PostgreSQL, MongoDB)
  • OpenAI or Anthropic API Keys
  • AWS Secret Access Keys

If your React app needs to perform an action that requires a secret key, you must route that request through a backend or a serverless function. The frontend calls your endpoint, and the server · which keeps the secret hidden · makes the final request. You can use our secret scanner to check if you have accidentally committed these to your repository.

2. Prevent Cross-Site Scripting (XSS)

React provides a layer of protection by automatically escaping values rendered in JSX. If a user tries to inject <script>alert(1)</script>, React renders it as literal text. However, developers often bypass this protection for convenience.

The Danger of dangerouslySetInnerHTML

This prop exists specifically to render raw HTML. If the content comes from a user (like a comment or profile bio), an attacker can execute malicious scripts in the context of your site.

URL-Based XSS

Even without dangerouslySetInnerHTML, you can be vulnerable if you allow users to provide links. An attacker can provide a URL starting with javascript:.

3. Secure React Server Components (RSC)

One specific risk is a Denial of Service (DoS) vulnerability. If you are using RSCs, ensure you are running the latest patched versions of react-server-dom-webpack or your specific bundler integration.

4. Move Beyond Client-Side Route Guards

A ProtectedRoute component in React is a User Experience (UX) tool, not a security tool. It prevents a user from *seeing* a dashboard, but it does not prevent them from *accessing* the data.

Anyone can open the Network tab in DevTools and see the API calls your app makes. If your backend does not verify the user's session token for every single request, your app is insecure.

5. Implement a Strict Content Security Policy (CSP)

A Content Security Policy is your last line of defense against XSS. It tells the browser exactly which domains are allowed to execute scripts on your page. For a React app, a strong CSP prevents unauthorized scripts from running even if an attacker finds an injection point.

You should use our CSP evaluator to test your current headers. A basic policy might look like this:

default-src 'self'; script-src 'self' https://apis.google.com; style-src 'self' 'unsafe-inline';

6. Audit and Pin Dependencies

React apps rely on a massive tree of npm packages. A single vulnerability in a deep dependency can compromise your entire application.

  • Run npm audit or yarn audit weekly.
  • Use a lockfile (package-lock.json) to ensure every developer and build server uses the exact same code.
  • Be wary of "ghost" dependencies · packages that are no longer maintained but still sit in your package.json.

7. Secure Form Submissions and CSRF

While React handles form state well, it doesn't automatically protect against Cross-Site Request Forgery (CSRF). If your API uses cookie-based authentication, an attacker could trick a user's browser into making a request to your API from a different site.

  • Use SameSite=Lax or Strict for all cookies.
  • For sensitive actions, implement a CSRF token or use custom headers (like X-Requested-With) which are not automatically sent by browsers during cross-site form submissions.
  • Always validate the shape of the data on the server using a library like Zod.

8. Sanitize State and Context Data

Avoid storing sensitive "God objects" in your React state or Redux store. If you store a full user object that includes a role: "admin" field or an internal ID, a user can modify that state using React DevTools.

While modifying local state won't give them admin rights on the server (if your backend is secure), it can allow them to bypass UI logic or see data they shouldn't. Only store the minimum data required for the UI to function.

9. Enforce HTTPS and Secure Headers

Encryption in transit is non-negotiable. Ensure your hosting provider (Vercel, Netlify, or AWS) enforces HTTPS. Beyond encryption, you should implement security headers that harden the browser environment:

  • Strict-Transport-Security (HSTS): Forces the browser to only use HTTPS.
  • X-Content-Type-Options: Prevents the browser from "sniffing" a response and executing it as a different MIME type.
  • X-Frame-Options: Prevents your site from being rendered in an iframe (protecting against clickjacking).

You can check these instantly with our security headers tool.

10. Validate Dynamic Imports and Code Splitting

If your React app uses dynamic imports (React.lazy or import()), ensure that the paths are not constructed using unvalidated user input. An attacker could potentially manipulate the path to load a malicious JavaScript module from your own server or a CDN if your configuration is loose.

Automating Your Security Review

Manual checklists are essential, but they are prone to human error · especially when "vibe-coding" at high speeds. SimplyScan provides a comprehensive vibe coding security checklist and an automated scanner that checks for these 10 vulnerabilities and more.

Our scanner grades 8 dimensions, including security, speed, and AEO (AI visibility), in about 30 seconds. It specifically detects exposed API keys, missing Supabase RLS, and broken authentication patterns that are common in AI-generated React code.

Summary Checklist

  • No secrets in .env files prefixed with VITE_ or REACT_APP_.
  • dangerouslySetInnerHTML is sanitized with DOMPurify.
  • All href and src attributes are validated.
  • Backend validates every request; frontend route guards are for UX only.
  • npm audit shows zero high-severity vulnerabilities.
  • Security headers (CSP, HSTS) are active.
  • All API communication happens over HTTPS.

Frequently asked questions

Does React protect against XSS automatically?

React automatically escapes values in JSX to prevent XSS, but this protection is bypassed if you use dangerouslySetInnerHTML or allow unvalidated user input in href attributes (e.g., javascript: links). Always sanitize HTML with DOMPurify and validate all dynamic URLs to ensure your application remains secure against injection attacks.

Are VITE_ and REACT_APP_ environment variables secret?

No. Any environment variable prefixed with VITE_ or REACT_APP_ is bundled into the public JavaScript. They are visible to anyone who opens their browser's DevTools. Use these only for public configuration like API URLs. Secret keys for OpenAI, Stripe, or databases must remain on a secure backend or serverless function.

Is a ProtectedRoute component enough to secure my app?

No. A ProtectedRoute is a UX feature that hides UI elements, but it does not secure data. Anyone can bypass your routing to call your API directly. Real security requires the server to verify a valid session token or JWT for every single request that returns sensitive information.

Where should I store authentication tokens in a React app?

The most secure method is using httpOnly cookies, which are inaccessible to JavaScript and protected from XSS theft. Avoid storing tokens in React state or localStorage where they can be easily inspected or stolen. Regardless of storage, ensure your backend validates the token on every sensitive API call.

Do I need a backend if my React app calls third-party APIs?

Yes, if the third-party API requires a secret key. Since React bundles are public, any secret key used directly in the frontend is compromised. Your React app should call your own backend, which then uses the secret key to communicate with the third-party service securely.

How do I check a React app for security issues quickly?

Start by running npm audit to find vulnerable packages and use a tool like SimplyScan to detect exposed secrets and missing security headers. Manually review all uses of dangerouslySetInnerHTML and ensure no sensitive logic is handled solely on the client side. Automated scans can identify these risks in seconds.

Related guides

  • Bolt.new Security Guide: 7 Vulnerabilities to Fix Before Launch · Bolt.new apps often ship with critical flaws like API keys bundled in client JavaScript and missing Supabase RLS policies. To secure your app, move secrets to server-side functions, scope RLS to auth.uid(), and enforce server-side authentication. SimplyScan finds these vulnerabilities in 30 seconds, helping you ship safely.
  • Web App Security Audit Checklist: 25 Checks Before Launch · A web app security audit requires checking 25 critical points across authentication, authorization, and infrastructure. SimplyScan's data shows 30% of AI-built apps have high-severity risks like exposed keys or missing RLS. Use this checklist to secure your vibe-coded apps before launch and ensure production-grade safety for your users.
  • Cursor App Security Checklist: 10 Things to Check Before You Ship · Before shipping a Cursor-built app, you must verify 10 critical security areas: eliminate hardcoded secrets, enforce RLS policies, implement server-side auth guards, validate all inputs, sanitize error messages, patch dependencies, configure security headers, restrict CORS origins, manage tokens in httpOnly cookies, and audit client-side logic for authorization bypasses.
  • React Security Best Practices for AI-Built Apps: Fixing Common Vibe-Coding Vulnerabilities · React security best practices in 2026 focus on preventing API key exposure and XSS in AI-generated code. Never store secret keys in client-side environment variables. Instead, use Next.js Server Components or proxy routes. Always enable Row Level Security (RLS) and sanitize dynamic HTML to protect against common vibe-coding vulnerabilities.

All security guides · Free security tools · Platform scanners · Security checklist