React Security Best Practices
Secure your React applications: XSS prevention, CSRF protection, authentication patterns, authorization strategies, secure data handling, Content Security Policy, and security auditing. Learn production-grade security practices.
🛡️ 1. XSS Prevention
Don't let hackers turn your app into their playground! 🎭 Cross-Site Scripting is one of the most common web attacks, but React has your back — if you know how to use its superpowers properly! 💪🔒
🔴 Impact: CRITICAL — XSS is the #1 web vulnerability — one slip-up with dangerouslySetInnerHTML and hackers own your users! 🚨
📋 In this section: React Auto-escaping • DOMPurify Sanitization • Safe Rich Text • URL Validation
// ❌ WRONG: Dangerous innerHTMLfunction UserComment({ comment }: { comment: string }) {// DANGEROUS: Allows XSS attacksreturn <div dangerouslySetInnerHTML={{ __html: comment }} />;}// ❌ WRONG: Unsanitized user inputfunction SearchResults({ query }: { query: string }) {return <div>Results for: {query}</div>; // If query contains <script>}
// ✅ CORRECT: React auto-escapes by defaultfunction UserComment({ comment }: { comment: string }) {// Safe: React escapes HTML automaticallyreturn <div>{comment}</div>;}// ✅ CORRECT: Sanitize if you need HTMLimport DOMPurify from 'dompurify';function RichComment({ html }: { html: string }) {const sanitized = DOMPurify.sanitize(html);return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;}// ✅ CORRECT: Use libraries for rich textimport { marked } from 'marked';import DOMPurify from 'dompurify';function MarkdownContent({ markdown }: { markdown: string }) {const html = marked(markdown);const sanitized = DOMPurify.sanitize(html);return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;}// ✅ CORRECT: Validate and sanitize URLsfunction SafeLink({ href, children }: { href: string; children: React.ReactNode }) {const isValidUrl = (url: string): boolean => {try {const urlObj = new URL(url);return ['http:', 'https:'].includes(urlObj.protocol);} catch {return false;}};if (!isValidUrl(href)) {return <span>{children}</span>;}return (<ahref={href}rel="noopener noreferrer"target="_blank">{children}</a>);}
🎭 2. CSRF Protection
Imagine someone forging your signature on a check — that's basically CSRF! 📝 These sneaky attacks trick authenticated users into unwanted actions. Let's lock it down with tokens and smart cookie policies! 🍪🔐
🔴 Impact: CRITICAL — Without CSRF protection, attackers can make your logged-in users do anything — transfer money, change passwords, you name it! 😱
📋 In this section: CSRF Tokens • Custom Hooks • Secure Fetch Wrapper • SameSite Cookies
// ✅ CSRF Token Implementationasync function getCsrfToken(): Promise<string> {const response = await fetch('/api/csrf-token', {credentials: 'include'});const { token } = await response.json();return token;}async function secureApiCall(data: any) {const csrfToken = await getCsrfToken();const response = await fetch('/api/endpoint', {method: 'POST',headers: {'Content-Type': 'application/json','X-CSRF-Token': csrfToken},credentials: 'include',body: JSON.stringify(data)});return response.json();}// ✅ Custom hook for CSRFfunction useCsrfToken() {const [token, setToken] = useState<string | null>(null);useEffect(() => {getCsrfToken().then(setToken);}, []);return token;}// ✅ Secure fetch wrapperfunction useSecureFetch() {const csrfToken = useCsrfToken();const secureFetch = useCallback(async (url: string,options: RequestInit = {}) => {if (!csrfToken) {throw new Error('CSRF token not available');}return fetch(url, {...options,headers: {...options.headers,'X-CSRF-Token': csrfToken},credentials: 'include'});}, [csrfToken]);return secureFetch;}// ✅ Next.js API Route (Server-side)// pages/api/csrf-token.tsimport { NextApiRequest, NextApiResponse } from 'next';import { v4 as uuidv4 } from 'uuid';export default function handler(req: NextApiRequest, res: NextApiResponse) {if (req.method === 'GET') {const token = uuidv4();// Store in httpOnly cookieres.setHeader('Set-Cookie',`csrf-token=${token}; HttpOnly; SameSite=Strict; Secure; Path=/`);res.json({ token });}}
🔑 3. Authentication Patterns
Who goes there?! 🏰 Authentication is your app's front gate — get it wrong and everyone walks right in! Let's build rock-solid auth with JWT, httpOnly cookies, and protected routes that even the sneakiest intruders can't bypass! 🚪🔒
🔴 Impact: CRITICAL — Broken auth is the #2 OWASP vulnerability — your entire app's security depends on getting this right! 🏗️
📋 In this section: Auth Context • Session Management • Protected Routes • Role-based Access
// ✅ Secure authentication contextimport { createContext, useContext, useState, useEffect } from 'react';interface AuthContextType {user: User | null;login: (email: string, password: string) => Promise<void>;logout: () => Promise<void>;isLoading: boolean;}const AuthContext = createContext<AuthContextType | undefined>(undefined);export function AuthProvider({ children }: { children: React.ReactNode }) {const [user, setUser] = useState<User | null>(null);const [isLoading, setIsLoading] = useState(true);useEffect(() => {// Check for existing sessioncheckSession();}, []);const checkSession = async () => {try {const response = await fetch('/api/auth/me', {credentials: 'include' // Include httpOnly cookies});if (response.ok) {const userData = await response.json();setUser(userData);}} catch (error) {console.error('Session check failed:', error);} finally {setIsLoading(false);}};const login = async (email: string, password: string) => {const response = await fetch('/api/auth/login', {method: 'POST',headers: { 'Content-Type': 'application/json' },credentials: 'include',body: JSON.stringify({ email, password })});if (!response.ok) {throw new Error('Login failed');}const userData = await response.json();setUser(userData);};const logout = async () => {await fetch('/api/auth/logout', {method: 'POST',credentials: 'include'});setUser(null);};return (<AuthContext.Provider value={{ user, login, logout, isLoading }}>{children}</AuthContext.Provider>);}// ✅ Protected route componentfunction ProtectedRoute({ children }: { children: React.ReactNode }) {const { user, isLoading } = useAuth();if (isLoading) {return <Spinner />;}if (!user) {return <Navigate to="/login" />;}return <>{children}</>;}// ✅ Role-based accessfunction RoleProtectedRoute({children,allowedRoles}: {children: React.ReactNode;allowedRoles: string[];}) {const { user } = useAuth();if (!user || !allowedRoles.includes(user.role)) {return <Navigate to="/unauthorized" />;}return <>{children}</>;}
📜 4. Content Security Policy
Think of CSP as your app's bouncer list — only approved scripts, styles, and resources get in! 🚧 It's the ultimate defense-in-depth layer that stops injection attacks even when other defenses fail. Set it up once, sleep better forever! 😴🛡️
🟠 Impact: HIGH — CSP is your last line of defense — even if XSS slips through, CSP blocks malicious scripts from executing! 🧱
📋 In this section: Next.js CSP Config • Security Headers • Permissions Policy • Meta Tags
// ✅ Next.js CSP Configuration// next.config.jsconst securityHeaders = [{key: 'Content-Security-Policy',value: ["default-src 'self'","script-src 'self' 'unsafe-eval' 'unsafe-inline'", // Adjust for your needs"style-src 'self' 'unsafe-inline'","img-src 'self' data: https:","font-src 'self' data:","connect-src 'self' https://api.example.com","frame-ancestors 'none'","base-uri 'self'","form-action 'self'"].join('; ')},{key: 'X-Frame-Options',value: 'DENY'},{key: 'X-Content-Type-Options',value: 'nosniff'},{key: 'Referrer-Policy',value: 'strict-origin-when-cross-origin'},{key: 'Permissions-Policy',value: 'camera=(), microphone=(), geolocation=()'}];module.exports = {async headers() {return [{source: '/:path*',headers: securityHeaders}];}};// ✅ Meta tags for CSPfunction SecurityMetaTags() {return (<><meta httpEquiv="Content-Security-Policy" content="default-src 'self'" /><meta httpEquiv="X-Content-Type-Options" content="nosniff" /><meta httpEquiv="X-Frame-Options" content="DENY" /></>);}