React Security Best Practices

Learn how to build secure React applications. Understand common security vulnerabilities and implement best practices to protect your users and data.

Why Security Matters in React Development

🔒 Protect User Data

Security vulnerabilities in React applications can expose sensitive user data, leading to identity theft, financial loss, and privacy breaches. A single XSS attack can compromise thousands of user accounts.

🛡️ Maintain Trust & Compliance

Security incidents damage your reputation and can result in legal consequences. Following security best practices ensures compliance with regulations like GDPR, CCPA, and helps maintain user trust in your application.

Advertisement Space - top-security

Google AdSense: horizontal

Common Security Threats

Cross-Site Scripting (XSS)

High

Malicious scripts executed in the browser through user input

❌ Vulnerable Code

1// ❌ Vulnerable to XSS
2function UserProfile({ user }) {
3 return (
4 <div>
5 <h1>Welcome, {user.name}!</h1>
6 {/* Dangerous: Direct HTML injection */}
7 <div dangerouslySetInnerHTML={{ __html: user.bio }} />
8
9 {/* Vulnerable: User input in URL */}
10 <a href={user.website}>Visit Website</a>
11
12 {/* Dangerous: Eval-like functions */}
13 <script>
14 const userSettings = {user.settings};
15 eval(userSettings.customScript);
16 </script>
17 </div>
18 );
19}
20
21// ❌ Vulnerable form handling
22function CommentForm() {
23 const [comment, setComment] = useState('');
24
25 const handleSubmit = () => {
26 // Dangerous: Direct injection
27 document.getElementById('comments').innerHTML += comment;
28 };
29
30 return (
31 <form onSubmit={handleSubmit}>
32 <textarea
33 value={comment}
34 onChange={(e) => setComment(e.target.value)}
35 />
36 <button type="submit">Submit</button>
37 </form>
38 );
39}

✅ Secure Solution

1// ✅ Safe implementation
2import DOMPurify from 'dompurify';
3
4function UserProfile({ user }) {
5 // Sanitize HTML content
6 const sanitizedBio = DOMPurify.sanitize(user.bio);
7
8 // Validate URLs
9 const isValidUrl = (url) => {
10 try {
11 const parsed = new URL(url);
12 return ['http:', 'https:'].includes(parsed.protocol);
13 } catch {
14 return false;
15 }
16 };
17
18 return (
19 <div>
20 <h1>Welcome, {user.name}!</h1>
21
22 {/* Safe HTML rendering */}
23 <div dangerouslySetInnerHTML={{ __html: sanitizedBio }} />
24
25 {/* Validated URL */}
26 {isValidUrl(user.website) && (
27 <a href={user.website} target="_blank" rel="noopener noreferrer">
28 Visit Website
29 </a>
30 )}
31
32 {/* Safe data handling */}
33 <script>
34 const userSettings = {JSON.stringify(user.settings)};
35 // Never use eval() - use JSON.parse instead
36 const settings = JSON.parse(userSettings);
37 </script>
38 </div>
39 );
40}
41
42// ✅ Safe form handling
43function CommentForm() {
44 const [comment, setComment] = useState('');
45 const [comments, setComments] = useState([]);
46
47 const handleSubmit = (e) => {
48 e.preventDefault();
49
50 // Safe: Use React state instead of direct DOM manipulation
51 setComments(prev => [...prev, {
52 id: Date.now(),
53 text: comment,
54 timestamp: new Date().toISOString()
55 }]);
56
57 setComment('');
58 };
59
60 return (
61 <div>
62 <form onSubmit={handleSubmit}>
63 <textarea
64 value={comment}
65 onChange={(e) => setComment(e.target.value)}
66 maxLength={1000} // Limit input length
67 />
68 <button type="submit">Submit</button>
69 </form>
70
71 <div id="comments">
72 {comments.map(comment => (
73 <div key={comment.id}>
74 {/* React automatically escapes text content */}
75 <p>{comment.text}</p>
76 <small>{comment.timestamp}</small>
77 </div>
78 ))}
79 </div>
80 </div>
81 );
82}

Prevention Strategies

  • 🛡️Use React's built-in XSS protection (automatic escaping)
  • 🛡️Sanitize HTML content with libraries like DOMPurify
  • 🛡️Validate all user inputs and URLs
  • 🛡️Use Content Security Policy (CSP) headers
  • 🛡️Avoid dangerouslySetInnerHTML when possible

Insecure Authentication

Critical

Weak authentication mechanisms that can be bypassed

❌ Vulnerable Code

1// ❌ Insecure authentication
2function LoginForm() {
3 const [credentials, setCredentials] = useState({ username: '', password: '' });
4
5 const handleLogin = async () => {
6 // Vulnerable: Sending credentials in URL
7 const response = await fetch(
8 `/api/login?username=${credentials.username}&password=${credentials.password}`
9 );
10
11 if (response.ok) {
12 const data = await response.json();
13
14 // Vulnerable: Storing sensitive data in localStorage
15 localStorage.setItem('userToken', data.token);
16 localStorage.setItem('userPassword', credentials.password);
17
18 // Vulnerable: Client-side role check only
19 if (data.role === 'admin') {
20 window.location.href = '/admin';
21 }
22 }
23 };
24
25 return (
26 <form>
27 <input
28 type="text"
29 value={credentials.username}
30 onChange={(e) => setCredentials({...credentials, username: e.target.value})}
31 />
32 <input
33 type="password"
34 value={credentials.password}
35 onChange={(e) => setCredentials({...credentials, password: e.target.value})}
36 />
37 <button type="button" onClick={handleLogin}>Login</button>
38 </form>
39 );
40}
41
42// ❌ Insecure route protection
43function AdminPanel() {
44 const userRole = localStorage.getItem('userRole');
45
46 // Vulnerable: Client-side only authorization
47 if (userRole !== 'admin') {
48 return <div>Access denied</div>;
49 }
50
51 return <div>Admin Panel</div>;
52}

✅ Secure Solution

1// ✅ Secure authentication
2import { useState, useContext } from 'react';
3
4const AuthContext = createContext();
5
6function LoginForm() {
7 const [credentials, setCredentials] = useState({ username: '', password: '' });
8 const [loading, setLoading] = useState(false);
9 const [error, setError] = useState('');
10 const { login } = useContext(AuthContext);
11
12 const handleLogin = async (e) => {
13 e.preventDefault();
14 setLoading(true);
15 setError('');
16
17 try {
18 // Secure: POST request with body
19 const response = await fetch('/api/login', {
20 method: 'POST',
21 headers: {
22 'Content-Type': 'application/json',
23 },
24 body: JSON.stringify(credentials),
25 });
26
27 if (response.ok) {
28 const data = await response.json();
29
30 // Secure: Use httpOnly cookies for tokens
31 // Token is automatically stored in httpOnly cookie by server
32
33 // Secure: Context-based state management
34 login(data.user);
35
36 // Secure: Server-side redirect or routing
37 window.location.href = data.redirectUrl;
38 } else {
39 setError('Invalid credentials');
40 }
41 } catch (err) {
42 setError('Login failed. Please try again.');
43 } finally {
44 setLoading(false);
45 }
46 };
47
48 return (
49 <form onSubmit={handleLogin}>
50 {error && <div className="error">{error}</div>}
51
52 <input
53 type="text"
54 value={credentials.username}
55 onChange={(e) => setCredentials({...credentials, username: e.target.value})}
56 required
57 autoComplete="username"
58 />
59
60 <input
61 type="password"
62 value={credentials.password}
63 onChange={(e) => setCredentials({...credentials, password: e.target.value})}
64 required
65 autoComplete="current-password"
66 />
67
68 <button type="submit" disabled={loading}>
69 {loading ? 'Logging in...' : 'Login'}
70 </button>
71 </form>
72 );
73}
74
75// ✅ Secure route protection
76function AdminPanel() {
77 const { user, loading } = useContext(AuthContext);
78
79 if (loading) {
80 return <div>Loading...</div>;
81 }
82
83 // Server-side authorization check is still required
84 if (!user || !user.permissions.includes('admin')) {
85 return <div>Access denied</div>;
86 }
87
88 return <div>Admin Panel</div>;
89}
90
91// ✅ Secure auth provider
92function AuthProvider({ children }) {
93 const [user, setUser] = useState(null);
94 const [loading, setLoading] = useState(true);
95
96 useEffect(() => {
97 // Verify authentication on app load
98 const verifyAuth = async () => {
99 try {
100 const response = await fetch('/api/me', {
101 credentials: 'include' // Include httpOnly cookies
102 });
103
104 if (response.ok) {
105 const userData = await response.json();
106 setUser(userData);
107 }
108 } catch (err) {
109 console.error('Auth verification failed:', err);
110 } finally {
111 setLoading(false);
112 }
113 };
114
115 verifyAuth();
116 }, []);
117
118 const login = (userData) => {
119 setUser(userData);
120 };
121
122 const logout = async () => {
123 await fetch('/api/logout', {
124 method: 'POST',
125 credentials: 'include'
126 });
127 setUser(null);
128 };
129
130 return (
131 <AuthContext.Provider value={{ user, login, logout, loading }}>
132 {children}
133 </AuthContext.Provider>
134 );
135}

Prevention Strategies

  • 🛡️Use HTTPS for all authentication endpoints
  • 🛡️Implement proper session management with httpOnly cookies
  • 🛡️Add CSRF protection for state-changing operations
  • 🛡️Validate authentication on both client and server
  • 🛡️Use secure password hashing (bcrypt, Argon2)

Sensitive Data Exposure

High

Accidentally exposing sensitive information in the client

❌ Vulnerable Code

1// ❌ Sensitive data exposure
2function UserDashboard() {
3 const [user, setUser] = useState(null);
4
5 useEffect(() => {
6 const fetchUser = async () => {
7 const response = await fetch('/api/user/profile');
8 const userData = await response.json();
9
10 // Dangerous: Exposing sensitive data
11 console.log('User data:', userData); // Could log sensitive info
12
13 setUser(userData);
14 };
15
16 fetchUser();
17 }, []);
18
19 return (
20 <div>
21 {/* Dangerous: Exposing sensitive data in client */}
22 <script>
23 window.USER_DATA = {JSON.stringify(user)};
24 </script>
25
26 {/* Dangerous: API keys in client code */}
27 <script>
28 const API_KEY = 'sk-1234567890abcdef';
29 const DATABASE_URL = 'mongodb://user:password@localhost:27017/myapp';
30 </script>
31
32 <h1>Welcome, {user?.name}</h1>
33
34 {/* Dangerous: Exposing internal IDs */}
35 <div data-user-id={user?.internalId} data-role={user?.role}>
36 {/* Dangerous: Showing sensitive information */}
37 <p>SSN: {user?.ssn}</p>
38 <p>Internal User ID: {user?.internalId}</p>
39 <p>Database Record ID: {user?.dbId}</p>
40 </div>
41 </div>
42 );
43}
44
45// ❌ Dangerous environment variables
46const API_URL = process.env.REACT_APP_API_URL;
47const SECRET_KEY = process.env.REACT_APP_SECRET_KEY; // Exposed to client!
48const DB_PASSWORD = process.env.REACT_APP_DB_PASSWORD; // Exposed to client!

✅ Secure Solution

1// ✅ Secure data handling
2function UserDashboard() {
3 const [user, setUser] = useState(null);
4
5 useEffect(() => {
6 const fetchUser = async () => {
7 const response = await fetch('/api/user/profile');
8 const userData = await response.json();
9
10 // Safe: Only log in development
11 if (process.env.NODE_ENV === 'development') {
12 console.log('User data loaded');
13 }
14
15 setUser(userData);
16 };
17
18 fetchUser();
19 }, []);
20
21 return (
22 <div>
23 <h1>Welcome, {user?.name}</h1>
24
25 {/* Safe: Only expose necessary data */}
26 <div data-user-type={user?.type}>
27 <p>Email: {user?.email}</p>
28 <p>Display Name: {user?.displayName}</p>
29 <p>Member Since: {user?.memberSince}</p>
30
31 {/* Safe: Masked sensitive data */}
32 {user?.phone && (
33 <p>Phone: {user.phone.replace(/(d{3})(d{3})(d{4})/, '($1) $2-****')}</p>
34 )}
35 </div>
36 </div>
37 );
38}
39
40// ✅ Safe environment variables
41const API_URL = process.env.REACT_APP_API_URL; // OK - Public API URL
42const APP_VERSION = process.env.REACT_APP_VERSION; // OK - Public version
43
44// ✅ Server-side configuration (not exposed to client)
45// These should be in server-side code only:
46// const SECRET_KEY = process.env.SECRET_KEY;
47// const DB_PASSWORD = process.env.DB_PASSWORD;
48
49// ✅ Secure data filtering
50function sanitizeUserData(userData) {
51 const {
52 // Remove sensitive fields
53 ssn,
54 internalId,
55 dbId,
56 hashedPassword,
57 ...publicData
58 } = userData;
59
60 return publicData;
61}

Prevention Strategies

  • 🛡️Never store secrets in client-side code
  • 🛡️Filter sensitive data before sending to client
  • 🛡️Use environment variables properly (REACT_APP_ prefix)
  • 🛡️Implement proper logging levels
  • 🛡️Regular security audits of exposed data

Dependency Vulnerabilities

Medium

Using packages with known security vulnerabilities

❌ Vulnerable Code

1// ❌ Vulnerable dependencies
2{
3 "dependencies": {
4 "react": "^16.8.0", // Outdated version
5 "lodash": "^4.17.11", // Known vulnerabilities
6 "moment": "^2.24.0", // Known vulnerabilities
7 "axios": "^0.18.0", // Outdated with vulnerabilities
8 "serialize-javascript": "^1.7.0", // XSS vulnerability
9 "handlebars": "^4.1.2", // Template injection
10 "marked": "^0.6.2", // XSS vulnerability
11 "js-yaml": "^3.12.0" // Code injection vulnerability
12 }
13}
14
15// ❌ Dangerous package usage
16import serialize from 'serialize-javascript';
17
18function App() {
19 const userData = { name: 'John', script: '<script>alert("XSS")</script>' };
20
21 return (
22 <div>
23 <script
24 dangerouslySetInnerHTML={{
25 __html: `window.USER_DATA = ${serialize(userData)}`
26 }}
27 />
28 </div>
29 );
30}

✅ Secure Solution

1// ✅ Updated and secure dependencies
2{
3 "dependencies": {
4 "react": "^18.2.0", // Latest stable version
5 "lodash": "^4.17.21", // Patched version
6 "date-fns": "^2.29.0", // Secure alternative to moment
7 "axios": "^1.2.0", // Latest secure version
8 "serialize-javascript": "^6.0.0", // Patched version
9 "handlebars": "^4.7.7", // Patched version
10 "marked": "^4.2.5", // Patched version
11 "js-yaml": "^4.1.0" // Patched version
12 },
13 "scripts": {
14 "audit": "npm audit",
15 "audit:fix": "npm audit fix",
16 "outdated": "npm outdated"
17 }
18}
19
20// ✅ Secure package usage
21import serialize from 'serialize-javascript';
22
23function App() {
24 const userData = { name: 'John', id: 123 };
25
26 return (
27 <div>
28 <script
29 dangerouslySetInnerHTML={{
30 __html: `window.USER_DATA = ${serialize(userData, { isJSON: true })}`
31 }}
32 />
33 </div>
34 );
35}
36
37// ✅ Regular security checks
38// package.json scripts
39{
40 "scripts": {
41 "security:audit": "npm audit --audit-level=moderate",
42 "security:update": "npm update",
43 "security:check": "npm outdated && npm audit"
44 }
45}

Prevention Strategies

  • 🛡️Regularly update dependencies
  • 🛡️Use npm audit to check for vulnerabilities
  • 🛡️Implement automated dependency scanning
  • 🛡️Use tools like Snyk or GitHub security alerts
  • 🛡️Pin dependency versions for critical applications

Advertisement Space - mid-security

Google AdSense: rectangle

Essential Security Tools

ESLint Security Plugin

Detect security vulnerabilities in your code

Installation:

npm install --save-dev eslint-plugin-security

Usage:

1// .eslintrc.js
2module.exports = {
3 plugins: ['security'],
4 extends: ['plugin:security/recommended'],
5 rules: {
6 'security/detect-object-injection': 'error',
7 'security/detect-eval-with-expression': 'error',
8 'security/detect-non-literal-regexp': 'error'
9 }
10};

Helmet.js

Set security headers for your React app

Installation:

npm install helmet

Usage:

1// server.js (for SSR)
2const helmet = require('helmet');
3
4app.use(helmet({
5 contentSecurityPolicy: {
6 directives: {
7 defaultSrc: ["'self'"],
8 styleSrc: ["'self'", "'unsafe-inline'"],
9 scriptSrc: ["'self'"],
10 imgSrc: ["'self'", "data:", "https:"]
11 }
12 }
13}));

DOMPurify

Sanitize HTML to prevent XSS attacks

Installation:

npm install dompurify

Usage:

1import DOMPurify from 'dompurify';
2
3function SafeHTML({ html }) {
4 const cleanHTML = DOMPurify.sanitize(html);
5
6 return (
7 <div dangerouslySetInnerHTML={{ __html: cleanHTML }} />
8 );
9}

npm audit

Built-in tool to check for vulnerable dependencies

Installation:

Built into npm

Usage:

1# Check for vulnerabilities
2npm audit
3
4# Fix vulnerabilities automatically
5npm audit fix
6
7# Check for specific severity levels
8npm audit --audit-level=high

Security Checklist

Authentication & Authorization

  • Use HTTPS everywhere
  • Implement proper session management
  • Use httpOnly cookies for tokens
  • Add CSRF protection
  • Validate authentication server-side
  • Implement proper password policies

Data Protection

  • Sanitize all user inputs
  • Use Content Security Policy
  • Avoid storing sensitive data in localStorage
  • Implement proper error handling
  • Use secure communication protocols
  • Validate all API responses

Code Security

  • Regular dependency updates
  • Use security linting tools
  • Avoid eval() and similar functions
  • Implement input validation
  • Use TypeScript for better type safety
  • Regular security code reviews

Deployment Security

  • Use environment variables properly
  • Implement proper logging
  • Use secure build processes
  • Regular security testing
  • Monitor for security incidents
  • Keep frameworks up to date

Advertisement Space - bottom-security

Google AdSense: horizontal