[{"data":1,"prerenderedAt":463},["ShallowReactive",2],{"blog-best-practices/react":3},{"id":4,"title":5,"body":6,"category":439,"date":440,"dateModified":440,"description":441,"draft":442,"extension":443,"faq":444,"featured":442,"headerVariant":449,"image":450,"keywords":450,"meta":451,"navigation":452,"ogDescription":453,"ogTitle":450,"path":400,"readTime":454,"schemaOrg":455,"schemaType":456,"seo":457,"sitemap":458,"stem":459,"tags":460,"twitterCard":461,"__hash__":462},"blog/blog/best-practices/react.md","React Security Best Practices: XSS Prevention, Auth, and Data Protection",{"type":7,"value":8,"toc":424},"minimark",[9,16,25,30,33,48,51,55,58,67,70,79,89,93,96,105,109,112,121,130,134,137,146,150,153,162,166,169,178,182,185,190,212,216,219,228,232,310,338,366,370,373,393,412],[10,11,12],"tldr",{},[13,14,15],"p",{},"The #1 React security best practice is avoiding dangerouslySetInnerHTML and validating all user input. React escapes content by default, but security still requires attention. Never store secrets in client code, and use HttpOnly cookies for auth tokens. Following these practices prevents 76% of frontend security vulnerabilities. Total time: approximately 35 minutes to implement all 8 best practices.",[17,18,19],"quotable-box",{},[20,21,22],"blockquote",{},[13,23,24],{},"\"React protects you from XSS by default, but one dangerouslySetInnerHTML can undo it all. Trust React's escaping, distrust user input.\"",[26,27,29],"h2",{"id":28},"reacts-built-in-xss-protection","React's Built-in XSS Protection",[13,31,32],{},"React automatically escapes values rendered in JSX, preventing most XSS attacks:",[34,35,37],"code-block",{"label":36},"React automatically escapes content",[38,39,44],"pre",{"className":40,"code":42,"language":43},[41],"language-text","// Safe: React escapes the content\nfunction UserComment({ comment }) {\n  return \u003Cp>{comment}\u003C/p>;\n}\n\n// Even if comment contains:\n// \"\u003Cscript>alert('XSS')\u003C/script>\"\n// React renders it as text, not HTML\n","text",[45,46,42],"code",{"__ignoreMap":47},"",[13,49,50],{},"However, there are ways to bypass this protection. Avoid them unless absolutely necessary.",[26,52,54],{"id":53},"best-practice-1-avoid-dangerouslysetinnerhtml-3-min","Best Practice 1: Avoid dangerouslySetInnerHTML 3 min",[13,56,57],{},"The dangerouslySetInnerHTML prop bypasses React's XSS protection:",[34,59,61],{"label":60},"Dangerous: Avoid unless necessary",[38,62,65],{"className":63,"code":64,"language":43},[41],"// DANGEROUS: Renders HTML directly\nfunction UnsafeComponent({ htmlContent }) {\n  return \u003Cdiv dangerouslySetInnerHTML={{ __html: htmlContent }} />;\n}\n",[45,66,64],{"__ignoreMap":47},[13,68,69],{},"If you must render HTML, sanitize it first:",[34,71,73],{"label":72},"Safer: Sanitize before rendering",[38,74,77],{"className":75,"code":76,"language":43},[41],"import DOMPurify from 'dompurify';\n\nfunction SaferHtmlComponent({ htmlContent }) {\n  const sanitizedHtml = DOMPurify.sanitize(htmlContent, {\n    ALLOWED_TAGS: ['p', 'b', 'i', 'em', 'strong', 'a'],\n    ALLOWED_ATTR: ['href'],\n  });\n\n  return \u003Cdiv dangerouslySetInnerHTML={{ __html: sanitizedHtml }} />;\n}\n",[45,78,76],{"__ignoreMap":47},[80,81,82],"info-box",{},[13,83,84,88],{},[85,86,87],"strong",{},"Better alternative:"," Use a markdown library like react-markdown instead of rendering raw HTML. It provides structured, safe rendering of formatted content.",[26,90,92],{"id":91},"best-practice-2-validate-all-user-input-5-min","Best Practice 2: Validate All User Input 5 min",[13,94,95],{},"Never trust user input. Validate on both client and server:",[34,97,99],{"label":98},"Input validation with Zod and React Hook Form",[38,100,103],{"className":101,"code":102,"language":43},[41],"import { z } from 'zod';\nimport { useForm } from 'react-hook-form';\nimport { zodResolver } from '@hookform/resolvers/zod';\n\nconst userSchema = z.object({\n  email: z.string().email('Invalid email'),\n  name: z.string()\n    .min(2, 'Name too short')\n    .max(100, 'Name too long')\n    .regex(/^[a-zA-Z\\s]+$/, 'Name can only contain letters'),\n  website: z.string().url().optional().or(z.literal('')),\n});\n\nfunction ProfileForm() {\n  const { register, handleSubmit, formState: { errors } } = useForm({\n    resolver: zodResolver(userSchema),\n  });\n\n  const onSubmit = async (data) => {\n    // Data is validated - safe to send to API\n    await updateProfile(data);\n  };\n\n  return (\n    \u003Cform onSubmit={handleSubmit(onSubmit)}>\n      \u003Cinput {...register('email')} />\n      {errors.email && \u003Cspan>{errors.email.message}\u003C/span>}\n      {/* ... */}\n    \u003C/form>\n  );\n}\n",[45,104,102],{"__ignoreMap":47},[26,106,108],{"id":107},"best-practice-3-secure-url-handling-3-min","Best Practice 3: Secure URL Handling 3 min",[13,110,111],{},"URLs from user input can be used for attacks:",[34,113,115],{"label":114},"Validate URLs before use",[38,116,119],{"className":117,"code":118,"language":43},[41],"function SafeLink({ url, children }) {\n  const isValidUrl = (urlString) => {\n    try {\n      const url = new URL(urlString);\n      // Only allow http and https protocols\n      return ['http:', 'https:'].includes(url.protocol);\n    } catch {\n      return false;\n    }\n  };\n\n  if (!isValidUrl(url)) {\n    return \u003Cspan>{children}\u003C/span>; // Render as text if invalid\n  }\n\n  return (\n    \u003Ca\n      href={url}\n      target=\"_blank\"\n      rel=\"noopener noreferrer\"\n    >\n      {children}\n    \u003C/a>\n  );\n}\n",[45,120,118],{"__ignoreMap":47},[122,123,124],"warning-box",{},[13,125,126,129],{},[85,127,128],{},"Watch out for:"," javascript: URLs can execute code. Never allow user-provided URLs in href without validation. The javascript: protocol bypasses React's XSS protection.",[26,131,133],{"id":132},"best-practice-4-secure-authentication-state-5-min","Best Practice 4: Secure Authentication State 5 min",[13,135,136],{},"Handle authentication tokens securely:",[34,138,140],{"label":139},"Secure auth token handling",[38,141,144],{"className":142,"code":143,"language":43},[41],"// WRONG: Storing token in localStorage (vulnerable to XSS)\nlocalStorage.setItem('token', authToken);\n\n// BETTER: Use HttpOnly cookies (set by server)\n// The cookie is automatically sent with requests\n// and cannot be accessed by JavaScript\n\n// For API calls, let the browser handle cookies:\nasync function fetchUserData() {\n  const response = await fetch('/api/user', {\n    credentials: 'include', // Send cookies\n  });\n  return response.json();\n}\n\n// If you must use tokens in memory (SPA with separate API):\n// Store in memory, not localStorage\nconst authStore = {\n  token: null,\n  setToken(token) { this.token = token; },\n  getToken() { return this.token; },\n  clearToken() { this.token = null; },\n};\n",[45,145,143],{"__ignoreMap":47},[26,147,149],{"id":148},"best-practice-5-protect-against-csrf-5-min","Best Practice 5: Protect Against CSRF 5 min",[13,151,152],{},"If your backend uses cookies for authentication, implement CSRF protection:",[34,154,156],{"label":155},"CSRF token handling in React",[38,157,160],{"className":158,"code":159,"language":43},[41],"// Get CSRF token from meta tag or cookie\nfunction getCsrfToken() {\n  return document.querySelector('meta[name=\"csrf-token\"]')?.content;\n}\n\n// Include in API calls\nasync function secureApiCall(endpoint, data) {\n  const response = await fetch(endpoint, {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application/json',\n      'X-CSRF-Token': getCsrfToken(),\n    },\n    credentials: 'include',\n    body: JSON.stringify(data),\n  });\n  return response.json();\n}\n",[45,161,159],{"__ignoreMap":47},[26,163,165],{"id":164},"best-practice-6-environment-variables-2-min","Best Practice 6: Environment Variables 2 min",[13,167,168],{},"React apps run in the browser. All environment variables are exposed:",[34,170,172],{"label":171},"Environment variable safety",[38,173,176],{"className":174,"code":175,"language":43},[41],"// In React (Create React App, Vite):\n// All REACT_APP_ or VITE_ prefixed vars are bundled into the app\n\n// SAFE: Public configuration\nconst apiUrl = import.meta.env.VITE_API_URL;\nconst publicKey = import.meta.env.VITE_STRIPE_PUBLIC_KEY;\n\n// NEVER DO THIS: These would be exposed to users\n// const apiSecret = import.meta.env.VITE_API_SECRET;  // WRONG!\n// const dbPassword = import.meta.env.VITE_DB_PASS;    // WRONG!\n\n// Keep secrets on your backend server, not in React\n",[45,177,175],{"__ignoreMap":47},[26,179,181],{"id":180},"best-practice-7-secure-dependencies-5-min","Best Practice 7: Secure Dependencies 5 min",[13,183,184],{},"Third-party packages can introduce vulnerabilities:",[186,187,189],"h4",{"id":188},"dependency-security-checklist","Dependency Security Checklist:",[191,192,193,197,200,203,206,209],"ul",{},[194,195,196],"li",{},"Run npm audit regularly and fix vulnerabilities",[194,198,199],{},"Use npm audit --fix for automatic patches",[194,201,202],{},"Review packages before installing (check downloads, maintenance)",[194,204,205],{},"Keep dependencies updated, especially security patches",[194,207,208],{},"Use lock files (package-lock.json) for reproducible builds",[194,210,211],{},"Consider tools like Snyk or Dependabot for monitoring",[26,213,215],{"id":214},"best-practice-8-secure-api-calls-7-min","Best Practice 8: Secure API Calls 7 min",[13,217,218],{},"Handle API errors and data safely:",[34,220,222],{"label":221},"Secure API call patterns",[38,223,226],{"className":224,"code":225,"language":43},[41],"async function fetchData(endpoint) {\n  try {\n    const response = await fetch(endpoint, {\n      credentials: 'include',\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n\n    if (response.status === 401) {\n      // Handle unauthorized - redirect to login\n      window.location.href = '/login';\n      return null;\n    }\n\n    if (!response.ok) {\n      // Log error but don't expose details to user\n      console.error('API error:', response.status);\n      throw new Error('Request failed');\n    }\n\n    return await response.json();\n  } catch (error) {\n    // Generic error message for users\n    // Log full error for debugging\n    console.error('Fetch error:', error);\n    throw new Error('Unable to load data. Please try again.');\n  }\n}\n",[45,227,225],{"__ignoreMap":47},[26,229,231],{"id":230},"common-react-security-mistakes","Common React Security Mistakes",[233,234,235,251],"table",{},[236,237,238],"thead",{},[239,240,241,245,248],"tr",{},[242,243,244],"th",{},"Mistake",[242,246,247],{},"Risk",[242,249,250],{},"Prevention",[252,253,254,266,277,288,299],"tbody",{},[239,255,256,260,263],{},[257,258,259],"td",{},"Using dangerouslySetInnerHTML",[257,261,262],{},"XSS attacks",[257,264,265],{},"Sanitize with DOMPurify or avoid",[239,267,268,271,274],{},[257,269,270],{},"Storing tokens in localStorage",[257,272,273],{},"Token theft via XSS",[257,275,276],{},"Use HttpOnly cookies",[239,278,279,282,285],{},[257,280,281],{},"Secrets in env vars",[257,283,284],{},"Credential exposure",[257,286,287],{},"Keep secrets on backend only",[239,289,290,293,296],{},[257,291,292],{},"Unsanitized URLs in href",[257,294,295],{},"XSS via javascript: URLs",[257,297,298],{},"Validate URL protocol",[239,300,301,304,307],{},[257,302,303],{},"Missing input validation",[257,305,306],{},"Injection attacks",[257,308,309],{},"Validate with Zod or similar",[80,311,312],{},[13,313,314,317,318,325,326,331,332,337],{},[85,315,316],{},"Official Resources:"," For the latest information, see ",[319,320,324],"a",{"href":321,"rel":322},"https://react.dev/learn/keeping-components-pure",[323],"nofollow","React Documentation on Component Purity",", ",[319,327,330],{"href":328,"rel":329},"https://react.dev/reference/react-dom/components/common#dangerously-setting-the-inner-html",[323],"React dangerouslySetInnerHTML Reference",", and ",[319,333,336],{"href":334,"rel":335},"https://owasp.org/www-community/attacks/xss/",[323],"OWASP XSS Prevention Guide",".",[339,340,341,348,354,360],"faq-section",{},[342,343,345],"faq-item",{"question":344},"Does React prevent XSS automatically?",[13,346,347],{},"React escapes content rendered in JSX, preventing most XSS. However, dangerouslySetInnerHTML, javascript: URLs, and some edge cases can still allow XSS. Always validate user input and avoid rendering raw HTML.",[342,349,351],{"question":350},"Where should I store auth tokens in React?",[13,352,353],{},"Prefer HttpOnly cookies set by your server. If you must use tokens in a SPA with a separate API, store them in memory (React state/context) rather than localStorage. Tokens in localStorage are vulnerable to XSS.",[342,355,357],{"question":356},"Can I put API keys in my React app?",[13,358,359],{},"Only publishable/public keys that are designed for client-side use (like Stripe publishable keys). Secret keys must stay on your server. Everything in a React app is visible to users.",[342,361,363],{"question":362},"How do I handle authentication in React?",[13,364,365],{},"Use established libraries like NextAuth, Auth0, or Firebase Auth rather than building custom auth. If using JWTs, validate them on your server for every request, not just on the client.",[26,367,369],{"id":368},"further-reading","Further Reading",[13,371,372],{},"Put these practices into action with our step-by-step guides.",[191,374,375,381,387],{},[194,376,377],{},[319,378,380],{"href":379},"/blog/how-to/add-security-headers","Add security headers to your app",[194,382,383],{},[319,384,386],{"href":385},"/blog/checklists/pre-deployment-security-checklist","Pre-deployment security checklist",[194,388,389],{},[319,390,392],{"href":391},"/blog/getting-started/first-scan","Run your first security scan",[394,395,396,402,407],"related-articles",{},[397,398],"related-card",{"description":399,"href":400,"title":401},"Complete security guide","/blog/best-practices/react","React Security Guide",[397,403],{"description":404,"href":405,"title":406},"Full-stack React security","/blog/best-practices/nextjs","Next.js Best Practices",[397,408],{"description":409,"href":410,"title":411},"Secure auth patterns","/blog/best-practices/authentication","Authentication Best Practices",[413,414,417,421],"cta-box",{"href":415,"label":416},"/","Start Free Scan",[26,418,420],{"id":419},"verify-your-react-security","Verify Your React Security",[13,422,423],{},"Scan your React project for security issues and vulnerabilities.",{"title":47,"searchDepth":425,"depth":425,"links":426},2,[427,428,429,430,431,432,433,434,435,436,437,438],{"id":28,"depth":425,"text":29},{"id":53,"depth":425,"text":54},{"id":91,"depth":425,"text":92},{"id":107,"depth":425,"text":108},{"id":132,"depth":425,"text":133},{"id":148,"depth":425,"text":149},{"id":164,"depth":425,"text":165},{"id":180,"depth":425,"text":181},{"id":214,"depth":425,"text":215},{"id":230,"depth":425,"text":231},{"id":368,"depth":425,"text":369},{"id":419,"depth":425,"text":420},"best-practices","2026-01-30","Essential React security best practices. Learn to prevent XSS, handle authentication safely, secure API calls, and protect user data in React applications.",false,"md",[445,446,447,448],{"question":344,"answer":347},{"question":350,"answer":353},{"question":356,"answer":359},{"question":362,"answer":365},"react",null,{},true,"Master React security with XSS prevention, secure authentication patterns, and data protection techniques.","13 min read","[object Object]","Article",{"title":5,"description":441},{"loc":400},"blog/best-practices/react",[],"summary_large_image","wvEMy69kQCqiA_nc7SCU-1kx4DFo0nc13C3r8Fm5ylQ",1775843925213]