Security Best Practices

1. Content Security Policy Implementation

CSP Directive Syntax Description Example Value
default-src default-src 'self' Fallback for all fetch directives; restricts all resources to same origin 'self' https://cdn.example.com
script-src script-src 'self' 'nonce-xyz' Controls valid sources for JavaScript execution 'self' 'unsafe-inline' https://apis.google.com
style-src style-src 'self' 'sha256-hash' Defines valid sources for stylesheets 'self' 'unsafe-inline' https://fonts.googleapis.com
img-src img-src 'self' data: https: Restricts sources for images; supports data URIs and wildcards 'self' data: https://*.cdn.com
connect-src connect-src 'self' wss: Controls fetch, XMLHttpRequest, WebSocket connections 'self' https://api.example.com wss://socket.io
font-src font-src 'self' data: Specifies valid sources for fonts 'self' https://fonts.gstatic.com data:
frame-src frame-src 'none' Restricts iframe embedding sources 'self' https://www.youtube.com
base-uri base-uri 'self' Limits URLs that can be used in <base> element 'self' (prevents base tag injection)
form-action form-action 'self' Restricts form submission targets 'self' https://secure-payment.com
upgrade-insecure-requests upgrade-insecure-requests Automatically upgrade HTTP requests to HTTPS No value needed (directive only)

Example: CSP implementation in HTML meta tag

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  
  <!-- Content Security Policy -->
  <meta http-equiv="Content-Security-Policy" 
        content="default-src 'self'; 
                 script-src 'self' 'nonce-2726c7f26c' https://cdn.example.com; 
                 style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; 
                 img-src 'self' data: https:; 
                 font-src 'self' https://fonts.gstatic.com; 
                 connect-src 'self' https://api.example.com; 
                 frame-src 'none'; 
                 base-uri 'self'; 
                 form-action 'self'; 
                 upgrade-insecure-requests;">
  
  <!-- Script with matching nonce -->
  <script nonce="2726c7f26c">
    console.log('This script is allowed by CSP');
  </script>
  
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto">
</head>
<body>
  <img src="image.jpg" alt="Allowed">
  <img src="https://external.com/image.jpg" alt="Also allowed">
</body>
</html>
CSP Keywords:
  • 'self' - Same origin only
  • 'none' - Block all sources
  • 'unsafe-inline' - Allow inline scripts/styles (avoid)
  • 'unsafe-eval' - Allow eval() (avoid)
  • 'nonce-{random}' - Cryptographic nonce for specific resources
  • 'sha256-{hash}' - Whitelist by content hash
Security Warning: Avoid 'unsafe-inline' and 'unsafe-eval' in production. Use nonces or hashes for inline scripts. Implement CSP in report-only mode first to detect violations before enforcing.

2. XSS Prevention and Input Sanitization

Technique Implementation Description Protection Level
HTML Entity Encoding Escape < > & " ' Convert special characters to HTML entities before output Essential - Prevents most XSS
Attribute Encoding Encode attribute values Escape quotes and special chars in HTML attributes Required for attribute injection
JavaScript Encoding Escape for JS context Properly encode data inserted into JavaScript Critical for dynamic JS
URL Encoding encodeURIComponent() Encode user input before adding to URLs Prevents URL injection
Content-Type Header text/html; charset=UTF-8 Explicitly set content type to prevent MIME sniffing Defense in depth
X-Content-Type-Options nosniff Prevent browser from MIME-sniffing responses Prevents XSS via uploads
DOMPurify Library Sanitize untrusted HTML Remove dangerous elements/attributes from user HTML For rich text content
Trusted Types API NEW Enforce type checking for DOM sinks Browser-enforced XSS protection for innerHTML, eval, etc. Chrome 83+, Edge 83+

Example: XSS prevention techniques

<!-- VULNERABLE: Direct user input insertion -->
<div id="output"></div>
<script>
  // NEVER do this:
  const userInput = '<img src=x onerror=alert("XSS")>';
  document.getElementById('output').innerHTML = userInput; // XSS!
</script>

<!-- SAFE: Use textContent for plain text -->
<div id="safe-output"></div>
<script>
  const userInput = '<img src=x onerror=alert("XSS")>';
  document.getElementById('safe-output').textContent = userInput; // Safe!
  // Output: &lt;img src=x onerror=alert("XSS")&gt;
</script>

<!-- SAFE: HTML entity encoding (server-side) -->
<div>
  <!-- User input: <script>alert('XSS')</script> -->
  <!-- Rendered: &lt;script&gt;alert('XSS')&lt;/script&gt; -->
  &lt;script&gt;alert('XSS')&lt;/script&gt;
</div>

<!-- SAFE: DOMPurify for rich content -->
<script src="https://cdn.jsdelivr.net/npm/dompurify/dist/purify.min.js"></script>
<script>
  const dirtyHTML = '<p>Safe content</p><script>alert("XSS")</script>';
  const clean = DOMPurify.sanitize(dirtyHTML);
  document.body.innerHTML = clean; // Only <p> tag remains
</script>

<!-- SAFE: Attribute encoding -->
<input type="text" value="user-input-here" data-user="escaped-value">

<!-- SAFE: URL encoding -->
<script>
  const searchTerm = 'user&input';
  const url = '/search?q=' + encodeURIComponent(searchTerm);
  // Result: /search?q=user%26input
</script>
Common XSS Attack Vectors:
  • innerHTML, outerHTML, document.write() with unsanitized input
  • Event handlers in attributes: <img onerror="...">, <body onload="...">
  • JavaScript URLs: <a href="javascript:alert(1)">
  • Data URIs: <img src="data:text/html,<script>...">
  • SVG script injection: <svg><script>alert(1)</script></svg>
  • CSS expression injection (IE legacy): style="expression(alert(1))"

3. CSRF Protection in Forms

Protection Method Implementation Description Effectiveness
CSRF Token <input type="hidden" name="csrf_token" value="random"> Server-generated unique token per session/request Most effective
SameSite Cookie Attribute Set-Cookie: session=abc; SameSite=Strict Prevent cookie from being sent in cross-site requests Strong defense
Double Submit Cookie Send token in both cookie and request body Server validates both match; no server-side state needed Stateless alternative
Custom Request Header X-Requested-With: XMLHttpRequest AJAX requests include custom header; can't be set cross-origin For API endpoints
Origin/Referer Validation Check Origin/Referer headers match Verify request comes from same origin Supplementary check
Re-authentication Require password for sensitive actions User must confirm identity for critical operations High-value transactions

Example: CSRF protection implementation

<!-- Method 1: Synchronizer Token Pattern -->
<form action="/transfer" method="POST">
  <!-- Server generates unique token per session -->
  <input type="hidden" name="csrf_token" value="8f7d6c5b4a3e2d1c">
  
  <label>Amount: <input type="number" name="amount" required></label>
  <label>To: <input type="text" name="recipient" required></label>
  <button type="submit">Transfer</button>
</form>

<!-- Method 2: Double Submit Cookie Pattern -->
<!-- Server sets cookie: csrf_token=abc123 -->
<form action="/delete-account" method="POST">
  <!-- Same value as cookie -->
  <input type="hidden" name="csrf_token" value="abc123">
  <button type="submit">Delete Account</button>
</form>

<!-- Method 3: SameSite Cookie (server response header) -->
<!-- 
  Set-Cookie: sessionId=xyz789; SameSite=Strict; Secure; HttpOnly
  
  SameSite values:
  - Strict: Never sent in cross-site requests
  - Lax: Sent on top-level navigation (GET only)
  - None: Always sent (requires Secure flag)
-->

<!-- Method 4: Custom header for AJAX -->
<script>
  fetch('/api/delete-user', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content,
      'X-Requested-With': 'XMLHttpRequest'
    },
    body: JSON.stringify({ userId: 123 })
  });
</script>

<!-- Store CSRF token in meta tag for JavaScript access -->
<meta name="csrf-token" content="8f7d6c5b4a3e2d1c">
Best Practices: Use multiple layers: SameSite cookies (Lax or Strict) + CSRF tokens for state-changing operations. For sensitive actions (money transfer, password change), add re-authentication. Always use POST/PUT/DELETE for state changes, never GET.

4. Secure Embedding and Sandboxing

Attribute/Directive Syntax Description Security Impact
sandbox (iframe) <iframe sandbox> Enable all restrictions (most secure default) Blocks scripts, forms, popups, same-origin access
sandbox="allow-scripts" <iframe sandbox="allow-scripts"> Allow JavaScript execution only Scripts can run but can't submit forms or open popups
sandbox="allow-same-origin" <iframe sandbox="allow-same-origin"> Allow same-origin access Can access cookies/storage of parent if same origin
sandbox="allow-forms" <iframe sandbox="allow-forms"> Allow form submission Forms can be submitted
sandbox="allow-popups" <iframe sandbox="allow-popups"> Allow popup windows Can open new windows/tabs
sandbox="allow-top-navigation" <iframe sandbox="allow-top-navigation"> Allow navigating top-level window Can change parent page URL (risky)
X-Frame-Options X-Frame-Options: DENY Prevent your page from being embedded Protects against clickjacking attacks
frame-ancestors (CSP) frame-ancestors 'none' Modern alternative to X-Frame-Options More flexible; supports multiple origins

Example: Secure iframe embedding

<!-- Most restrictive: Completely sandboxed (no scripts, forms, or navigation) -->
<iframe src="untrusted.html" sandbox></iframe>

<!-- Allow scripts but nothing else -->
<iframe src="third-party-widget.html" 
        sandbox="allow-scripts">
</iframe>

<!-- Allow scripts and forms (common for embedded content) -->
<iframe src="https://external.com/embed" 
        sandbox="allow-scripts allow-forms allow-popups"
        loading="lazy">
</iframe>

<!-- DANGEROUS: allow-same-origin + allow-scripts = no sandbox protection! -->
<!-- Iframe can remove its own sandbox attribute via JavaScript -->
<iframe src="malicious.html" 
        sandbox="allow-same-origin allow-scripts"> <!-- AVOID THIS -->
</iframe>

<!-- Prevent your page from being embedded (clickjacking protection) -->
<meta http-equiv="X-Frame-Options" content="DENY">
<!-- Or allow only same origin: -->
<meta http-equiv="X-Frame-Options" content="SAMEORIGIN">

<!-- Modern CSP alternative (preferred) -->
<meta http-equiv="Content-Security-Policy" 
      content="frame-ancestors 'none';">
<!-- Or whitelist specific origins: -->
<meta http-equiv="Content-Security-Policy" 
      content="frame-ancestors 'self' https://trusted.com;">

<!-- Secure YouTube embed example -->
<iframe src="https://www.youtube-nocookie.com/embed/VIDEO_ID"
        sandbox="allow-scripts allow-same-origin allow-presentation"
        allow="accelerometer; encrypted-media; gyroscope; picture-in-picture"
        loading="lazy"
        width="560"
        height="315">
</iframe>
Critical Security Rule: NEVER combine allow-same-origin and allow-scripts when embedding untrusted content. This combination allows the iframe to remove its own sandbox restrictions, completely bypassing security.

5. HTTP Security Headers

Header Value/Directive Description Implementation
Strict-Transport-Security (HSTS) max-age=31536000; includeSubDomains; preload Force HTTPS for all future requests Essential for HTTPS sites
X-Content-Type-Options nosniff Prevent MIME-type sniffing Always enable
X-Frame-Options DENY | SAMEORIGIN Clickjacking protection (legacy, use CSP) Use with CSP frame-ancestors
X-XSS-Protection 0 Disable legacy XSS filter (can cause vulnerabilities) Set to 0
Referrer-Policy strict-origin-when-cross-origin Control Referer header information leakage Recommended
Permissions-Policy geolocation=(), camera=(), microphone=() Control browser features and APIs access Feature restrictions
Cross-Origin-Embedder-Policy (COEP) require-corp Require explicit opt-in for cross-origin resources Enables SharedArrayBuffer
Cross-Origin-Opener-Policy (COOP) same-origin Isolate browsing context from cross-origin windows Spectre mitigation
Cross-Origin-Resource-Policy (CORP) same-site | same-origin | cross-origin Control which sites can embed resource Resource isolation

Example: Security headers in HTML meta tags

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  
  <!-- Content Security Policy -->
  <meta http-equiv="Content-Security-Policy" 
        content="default-src 'self'; script-src 'self' 'nonce-xyz'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'none';">
  
  <!-- Prevent MIME sniffing -->
  <meta http-equiv="X-Content-Type-Options" content="nosniff">
  
  <!-- Clickjacking protection -->
  <meta http-equiv="X-Frame-Options" content="DENY">
  
  <!-- Disable legacy XSS filter (prevents vulnerabilities) -->
  <meta http-equiv="X-XSS-Protection" content="0">
  
  <!-- Referrer policy -->
  <meta name="referrer" content="strict-origin-when-cross-origin">
  
  <!-- Permissions policy (control browser features) -->
  <meta http-equiv="Permissions-Policy" 
        content="geolocation=(), camera=(), microphone=(), payment=()">
</head>
<body>
  <!-- Content -->
</body>
</html>

<!-- Note: While meta tags work for CSP and some headers, 
     HTTP response headers are preferred and more reliable.
     Configure these in server settings:
     
     Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
     X-Content-Type-Options: nosniff
     X-Frame-Options: DENY
     Content-Security-Policy: default-src 'self'
     Referrer-Policy: strict-origin-when-cross-origin
     Permissions-Policy: geolocation=(), camera=()
     Cross-Origin-Opener-Policy: same-origin
     Cross-Origin-Embedder-Policy: require-corp
-->
Referrer-Policy Value Behavior Use Case
no-referrer Never send Referer header Maximum privacy, may break analytics
no-referrer-when-downgrade Send full URL except HTTPS→HTTP Default browser behavior
origin Send only origin (no path) Balance privacy and functionality
strict-origin-when-cross-origin Full URL for same-origin; origin only cross-origin Recommended default
same-origin Send Referer only to same origin High privacy, may affect external integrations

6. Form Security and Data Protection

Security Measure Implementation Description Protection Against
HTTPS Only <form action="https://..." method="POST"> Always submit forms over encrypted connection Man-in-the-middle, eavesdropping
autocomplete="off" <input autocomplete="off"> Disable browser autofill for sensitive fields Credential theft from shared computers
autocomplete="new-password" <input type="password" autocomplete="new-password"> Prevent autofill of existing passwords Password manager confusion
maxlength Attribute <input maxlength="50"> Limit input length (client-side) Buffer overflow, DoS (supplement server validation)
input type Validation <input type="email|url|tel"> Browser validates input format Malformed data (basic validation only)
pattern Attribute <input pattern="[A-Za-z]{3,}"> Regex validation on client side Invalid formats (must validate server-side too)
novalidate Attribute <form novalidate> Disable HTML5 validation (use custom JS) N/A - allows custom validation logic
rel="noopener noreferrer" <form target="_blank" rel="noopener"> Prevent window.opener access Reverse tabnabbing attacks
Rate Limiting Server-side throttling Limit submissions per IP/user Brute force, spam, DoS attacks
CAPTCHA reCAPTCHA, hCaptcha integration Distinguish humans from bots Automated spam, credential stuffing

Example: Secure form implementation

<!-- Secure login form -->
<form action="https://example.com/login" 
      method="POST" 
      autocomplete="off">
  
  <!-- CSRF token -->
  <input type="hidden" name="csrf_token" value="secure-random-token">
  
  <!-- Username/email field -->
  <label for="email">Email:</label>
  <input type="email" 
         id="email" 
         name="email" 
         required 
         autocomplete="username"
         maxlength="100"
         pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$">
  
  <!-- Password field -->
  <label for="password">Password:</label>
  <input type="password" 
         id="password" 
         name="password" 
         required 
         autocomplete="current-password"
         minlength="8"
         maxlength="128">
  
  <button type="submit">Login</button>
</form>

<!-- Secure registration form -->
<form action="https://example.com/register" 
      method="POST" 
      autocomplete="off">
  
  <input type="hidden" name="csrf_token" value="secure-random-token">
  
  <label for="new-email">Email:</label>
  <input type="email" 
         id="new-email" 
         name="email" 
         required 
         autocomplete="email"
         maxlength="100">
  
  <label for="new-password">Password:</label>
  <input type="password" 
         id="new-password" 
         name="password" 
         required 
         autocomplete="new-password"
         minlength="8"
         aria-describedby="password-requirements">
  
  <div id="password-requirements" class="note">
    Must be at least 8 characters with uppercase, lowercase, number, and special character
  </div>
  
  <!-- CAPTCHA -->
  <div class="g-recaptcha" data-sitekey="your-site-key"></div>
  
  <button type="submit">Register</button>
</form>

<!-- Payment form (extra security) -->
<form action="https://secure-payment.com/process" 
      method="POST" 
      autocomplete="off"
      target="_blank"
      rel="noopener noreferrer">
  
  <input type="hidden" name="csrf_token" value="secure-token">
  
  <label for="card">Card Number:</label>
  <input type="text" 
         id="card" 
         name="card_number" 
         required 
         autocomplete="cc-number"
         pattern="[0-9]{13,19}"
         maxlength="19"
         inputmode="numeric">
  
  <label for="cvv">CVV:</label>
  <input type="password" 
         id="cvv" 
         name="cvv" 
         required 
         autocomplete="cc-csc"
         pattern="[0-9]{3,4}"
         maxlength="4"
         inputmode="numeric">
  
  <button type="submit">Pay Securely</button>
</form>
Autocomplete Values for Security:
  • username - Email/username field
  • current-password - Login password
  • new-password - Registration/change password
  • one-time-code - 2FA/OTP codes
  • cc-number - Credit card number
  • cc-csc - Card security code
  • off - Disable completely (sensitive data)
Never Trust Client-Side Validation:
  • Always validate and sanitize on server
  • HTML5 validation can be bypassed
  • Use client validation for UX only
  • Implement server-side rate limiting
  • Log and monitor suspicious activity
  • Never store sensitive data in localStorage

Security Best Practices Checklist

Critical Reminders: Client-side security is defense in depth only - never trust the client. Always validate, sanitize, and authorize on the server. Security is a process, not a feature - regular audits and updates are essential.