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" /></>);}