Frontend Security Implementation Patterns

1. OWASP Frontend Top 10 XSS CSRF

Vulnerability Attack Type Risk Level Prevention
XSS - Cross-Site Scripting Inject malicious scripts into trusted websites CRITICAL Escape output, CSP headers, DOMPurify sanitization
CSRF - Cross-Site Request Forgery Execute unwanted actions on authenticated user's behalf HIGH CSRF tokens, SameSite cookies, double submit pattern
Clickjacking Trick users into clicking hidden malicious elements MEDIUM X-Frame-Options: DENY, CSP frame-ancestors directive
Open Redirect Redirect users to malicious external sites MEDIUM Validate redirect URLs, whitelist allowed domains
DOM-based XSS Manipulate client-side JavaScript to inject scripts CRITICAL Avoid innerHTML, use textContent, sanitize user input
Sensitive Data Exposure Expose tokens, keys, or PII in client-side code HIGH Never store secrets client-side, HttpOnly cookies, encryption

Example: XSS and CSRF Prevention

// ❌ Vulnerable to XSS
function displayUserInput(input) {
  document.getElementById('output').innerHTML = input;
  // Attacker input: <script>alert('XSS')</script>
}

// ✅ Safe: Use textContent
function displayUserInputSafe(input) {
  document.getElementById('output').textContent = input;
  // Scripts won't execute
}

// ✅ React automatically escapes
function UserProfile({ name }) {
  return <div>{name}</div>; // Safe by default
}

// ❌ Dangerous: dangerouslySetInnerHTML
function RichText({ html }) {
  return <div dangerouslySetInnerHTML={{ __html: html }} />;
  // Can execute malicious scripts
}

// ✅ Safe: Sanitize with DOMPurify
import DOMPurify from 'dompurify';

function SafeRichText({ html }) {
  const clean = DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'],
    ALLOWED_ATTR: ['href']
  });
  
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}

// CSRF Protection - Add token to requests
// Backend sets CSRF token in cookie
// Frontend includes token in headers
function apiRequest(url, data) {
  const csrfToken = getCookie('csrf-token');
  
  return fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-Token': csrfToken
    },
    credentials: 'include', // Send cookies
    body: JSON.stringify(data)
  });
}

// Double Submit Cookie Pattern
function getCookie(name) {
  const value = `; ${document.cookie}`;
  const parts = value.split(`; ${name}=`);
  if (parts.length === 2) return parts.pop().split(';').shift();
}

// SameSite Cookie (Backend sets)
Set-Cookie: sessionId=abc123; SameSite=Strict; Secure; HttpOnly

// Axios CSRF configuration
import axios from 'axios';

axios.defaults.xsrfCookieName = 'csrf-token';
axios.defaults.xsrfHeaderName = 'X-CSRF-Token';
axios.defaults.withCredentials = true;

// Prevent Clickjacking
// Set in HTTP headers (backend)
X-Frame-Options: DENY
// or
Content-Security-Policy: frame-ancestors 'none'

// Validate Redirects
function safeRedirect(url) {
  const allowedDomains = ['example.com', 'app.example.com'];
  try {
    const urlObj = new URL(url, window.location.origin);
    if (allowedDomains.includes(urlObj.hostname)) {
      window.location.href = url;
    } else {
      console.error('Redirect not allowed:', url);
    }
  } catch (e) {
    console.error('Invalid URL:', url);
  }
}

// DOM-based XSS Prevention
// ❌ Dangerous
element.innerHTML = userInput;
location.href = userInput;
eval(userInput);

// ✅ Safe alternatives
element.textContent = userInput;
element.setAttribute('href', sanitizedUrl);
// Never use eval()

// Input Validation
function validateEmail(email) {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return regex.test(email);
}

function sanitizeInput(input) {
  return input
    .trim()
    .replace(/[<>"']/g, (char) => {
      const escapeChars = {
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#x27;'
      };
      return escapeChars[char];
    });
}

XSS Types

Type Attack Vector
Stored XSS Malicious script stored in database
Reflected XSS Script in URL, reflected by server
DOM XSS Client-side script manipulation

Security Headers

  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • X-XSS-Protection: 1; mode=block
  • Referrer-Policy: strict-origin
  • Permissions-Policy: Feature control
Never Trust User Input: Always validate and sanitize on both client and server. Client-side validation is for UX, server-side is for security.

2. Content Security Policy CSP Headers

Directive Purpose Example Value Use Case
default-src Fallback for all fetch directives 'self' Only allow resources from same origin
script-src Valid sources for JavaScript 'self' 'nonce-abc123' Prevent inline scripts, XSS attacks
style-src Valid sources for stylesheets 'self' 'unsafe-inline' Allow same-origin and inline styles
img-src Valid sources for images 'self' https://cdn.example.com Allow images from specific domains
connect-src Valid sources for fetch, XHR, WebSocket 'self' https://api.example.com Restrict API endpoints
frame-ancestors Valid sources that can embed this page 'none' Prevent clickjacking
upgrade-insecure-requests Upgrade HTTP to HTTPS (no value) Force HTTPS for all resources

Example: CSP Implementation

// Basic CSP Header (backend)
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' https://cdn.example.com

// Strict CSP with nonces
// Backend generates random nonce for each request
const nonce = crypto.randomBytes(16).toString('base64');

Content-Security-Policy: 
  default-src 'self';
  script-src 'self' 'nonce-${nonce}';
  style-src 'self' 'nonce-${nonce}';
  img-src 'self' https: data:;
  font-src 'self';
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  upgrade-insecure-requests;

// HTML with nonce
<script nonce="${nonce}">
  console.log('This script is allowed');
</script>

// CSP Meta Tag (not recommended for production)
<meta http-equiv="Content-Security-Policy" 
  content="default-src 'self'; script-src 'self'">

// Report-Only Mode (testing)
Content-Security-Policy-Report-Only: 
  default-src 'self';
  report-uri /csp-violation-report

// CSP Violation Reporting
Content-Security-Policy: 
  default-src 'self';
  report-uri https://example.report-uri.com/r/d/csp/enforce

// Handle CSP violations
document.addEventListener('securitypolicyviolation', (e) => {
  console.log('CSP Violation:', {
    blockedURI: e.blockedURI,
    violatedDirective: e.violatedDirective,
    originalPolicy: e.originalPolicy
  });
  
  // Send to logging service
  fetch('/csp-report', {
    method: 'POST',
    body: JSON.stringify({
      blockedURI: e.blockedURI,
      violatedDirective: e.violatedDirective
    })
  });
});

// Next.js CSP Configuration
// next.config.js
const cspHeader = `
  default-src 'self';
  script-src 'self' 'unsafe-eval' 'unsafe-inline';
  style-src 'self' 'unsafe-inline';
  img-src 'self' blob: data:;
  font-src 'self';
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';
  upgrade-insecure-requests;
`;

module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: cspHeader.replace(/\s{2,}/g, ' ').trim()
          }
        ]
      }
    ];
  }
};

// React Helmet for CSP
import { Helmet } from 'react-helmet';

function App() {
  return (
    <Helmet>
      <meta httpEquiv="Content-Security-Policy" 
        content="default-src 'self'; script-src 'self'" />
    </Helmet>
  );
}

CSP Levels

Level Strictness Example
Level 1 - Permissive Allows most sources default-src *
Level 2 - Moderate Restricts to trusted domains default-src 'self' cdn.com
Level 3 - Strict Nonce/hash-based script-src 'nonce-xxx'

Common CSP Issues

  • Inline scripts blocked: Use nonces or external files
  • eval() blocked: Avoid eval, use JSON.parse
  • CDN resources: Add CDN domains to directives
  • Google Analytics: Add google-analytics.com
  • Fonts blocked: Add font CDN to font-src
CSP Benefits: Blocks 99% of XSS attacks. Start with report-only mode to identify violations before enforcement.

3. JWT Token Storage HttpOnly Cookies

Storage Method Security XSS Vulnerable CSRF Vulnerable Use Case
HttpOnly Cookie BEST ❌ No ✅ Yes (mitigated with SameSite) Recommended for authentication tokens
localStorage POOR ✅ Yes ❌ No Non-sensitive data, user preferences
sessionStorage POOR ✅ Yes ❌ No Temporary session data, cleared on tab close
Memory (React state) Good ❌ No ❌ No Lost on refresh, single-page apps
Secure + HttpOnly + SameSite EXCELLENT ❌ No ❌ No Production authentication, maximum security

Example: Secure JWT Token Handling

// ❌ INSECURE: localStorage JWT storage
function login(credentials) {
  const response = await fetch('/api/login', {
    method: 'POST',
    body: JSON.stringify(credentials)
  });
  
  const { token } = await response.json();
  localStorage.setItem('token', token); // Vulnerable to XSS!
}

// ✅ SECURE: HttpOnly Cookie (Backend)
// Express.js backend
app.post('/api/login', async (req, res) => {
  const { email, password } = req.body;
  const user = await authenticate(email, password);
  
  if (user) {
    const token = jwt.sign({ userId: user.id }, SECRET);
    
    res.cookie('token', token, {
      httpOnly: true,     // Not accessible via JavaScript
      secure: true,       // Only sent over HTTPS
      sameSite: 'strict', // CSRF protection
      maxAge: 3600000     // 1 hour
    });
    
    res.json({ success: true, user });
  } else {
    res.status(401).json({ error: 'Invalid credentials' });
  }
});

// Frontend: Cookie sent automatically
function fetchProtectedData() {
  return fetch('/api/protected', {
    credentials: 'include' // Send cookies
  });
}

// Refresh Token Pattern (Best Practice)
// Backend: Access token (short-lived) + Refresh token (long-lived)
app.post('/api/login', async (req, res) => {
  const user = await authenticate(req.body);
  
  const accessToken = jwt.sign({ userId: user.id }, SECRET, { expiresIn: '15m' });
  const refreshToken = jwt.sign({ userId: user.id }, REFRESH_SECRET, { expiresIn: '7d' });
  
  // Store refresh token in database
  await saveRefreshToken(user.id, refreshToken);
  
  // Send both as HttpOnly cookies
  res.cookie('accessToken', accessToken, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 15 * 60 * 1000 // 15 minutes
  });
  
  res.cookie('refreshToken', refreshToken, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
    path: '/api/refresh' // Only sent to refresh endpoint
  });
  
  res.json({ success: true });
});

// Refresh endpoint
app.post('/api/refresh', async (req, res) => {
  const { refreshToken } = req.cookies;
  
  if (!refreshToken) {
    return res.status(401).json({ error: 'No refresh token' });
  }
  
  try {
    const decoded = jwt.verify(refreshToken, REFRESH_SECRET);
    const isValid = await validateRefreshToken(decoded.userId, refreshToken);
    
    if (isValid) {
      const newAccessToken = jwt.sign({ userId: decoded.userId }, SECRET, { expiresIn: '15m' });
      
      res.cookie('accessToken', newAccessToken, {
        httpOnly: true,
        secure: true,
        sameSite: 'strict',
        maxAge: 15 * 60 * 1000
      });
      
      res.json({ success: true });
    } else {
      res.status(401).json({ error: 'Invalid refresh token' });
    }
  } catch (error) {
    res.status(401).json({ error: 'Token expired' });
  }
});

// Frontend: Axios interceptor for auto-refresh
import axios from 'axios';

axios.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;
    
    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
      
      try {
        await axios.post('/api/refresh', {}, { withCredentials: true });
        return axios(originalRequest);
      } catch (refreshError) {
        // Refresh failed, redirect to login
        window.location.href = '/login';
        return Promise.reject(refreshError);
      }
    }
    
    return Promise.reject(error);
  }
);

// Logout (Clear cookies)
app.post('/api/logout', (req, res) => {
  res.clearCookie('accessToken');
  res.clearCookie('refreshToken');
  res.json({ success: true });
});

// BFF (Backend for Frontend) Pattern
// API Gateway handles tokens, frontend never sees them
// Frontend → BFF → Microservices
// BFF stores tokens securely, frontend uses session
Attribute Purpose
HttpOnly Blocks JavaScript access
Secure HTTPS only transmission
SameSite=Strict No cross-site requests
SameSite=Lax Allow top-level navigation
SameSite=None Allow all (requires Secure)
Path Restrict cookie scope
Domain Subdomain sharing

Token Storage Comparison

Method XSS CSRF
localStorage ❌ Vulnerable ✅ Safe
Cookie (HttpOnly) ✅ Safe ⚠️ Mitigate
Memory only ✅ Safe ✅ Safe
Never Store Sensitive Data in localStorage: Any XSS vulnerability can steal tokens. Use HttpOnly cookies with SameSite=Strict for maximum security.

4. Input Validation Sanitization DOMPurify

Library/Tool Purpose Use Case Example
DOMPurify Sanitize HTML, prevent XSS Rich text editors, user-generated HTML DOMPurify.sanitize(html)
validator.js Validate and sanitize strings Email, URL, credit card validation validator.isEmail(input)
Yup / Zod Schema validation Form validation, type safety schema.parse(data)
sanitize-html HTML sanitization with config Node.js backend, allowlist tags sanitizeHtml(html, options)
OWASP Java Encoder Context-aware encoding Backend Java applications Encode.forHtml(input)

Example: Input Validation and Sanitization

// DOMPurify - Sanitize HTML
import DOMPurify from 'dompurify';

function RichTextDisplay({ html }) {
  const cleanHtml = DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['p', 'b', 'i', 'em', 'strong', 'a', 'ul', 'ol', 'li'],
    ALLOWED_ATTR: ['href', 'title'],
    ALLOW_DATA_ATTR: false
  });
  
  return <div dangerouslySetInnerHTML={{ __html: cleanHtml }} />;
}

// Strict sanitization
const strictClean = DOMPurify.sanitize(html, {
  ALLOWED_TAGS: [], // No tags allowed, text only
  ALLOWED_ATTR: []
});

// Allow specific attributes
const customClean = DOMPurify.sanitize(html, {
  ALLOWED_TAGS: ['a', 'img'],
  ALLOWED_ATTR: ['href', 'src', 'alt'],
  ALLOWED_URI_REGEXP: /^https?:/ // Only http/https URLs
});

// validator.js - String validation
import validator from 'validator';

function validateUserInput(input) {
  const errors = {};
  
  if (!validator.isEmail(input.email)) {
    errors.email = 'Invalid email address';
  }
  
  if (!validator.isURL(input.website, { protocols: ['http', 'https'] })) {
    errors.website = 'Invalid URL';
  }
  
  if (!validator.isStrongPassword(input.password, {
    minLength: 8,
    minLowercase: 1,
    minUppercase: 1,
    minNumbers: 1,
    minSymbols: 1
  })) {
    errors.password = 'Weak password';
  }
  
  return errors;
}

// Sanitize strings
const clean = validator.escape(input); // Escape HTML entities
const trimmed = validator.trim(input);
const normalized = validator.normalizeEmail(email);

// Zod - Schema validation (TypeScript)
import { z } from 'zod';

const userSchema = z.object({
  email: z.string().email(),
  age: z.number().min(18).max(120),
  username: z.string().min(3).max(20).regex(/^[a-zA-Z0-9_]+$/),
  website: z.string().url().optional(),
  role: z.enum(['user', 'admin', 'moderator'])
});

function validateUser(data) {
  try {
    const validated = userSchema.parse(data);
    return { success: true, data: validated };
  } catch (error) {
    return { success: false, errors: error.errors };
  }
}

// Yup - Schema validation (React)
import * as yup from 'yup';

const loginSchema = yup.object({
  email: yup.string().email('Invalid email').required('Email required'),
  password: yup.string().min(8, 'Min 8 characters').required('Password required')
});

// Formik integration
import { Formik } from 'formik';

<Formik
  initialValues={{ email: '', password: '' }}
  validationSchema={loginSchema}
  onSubmit={(values) => console.log(values)}
>
  {/* Form fields */}
</Formik>

// Custom validation functions
function sanitizeFilename(filename) {
  return filename
    .replace(/[^a-zA-Z0-9.-]/g, '_') // Replace special chars
    .replace(/\.{2,}/g, '.') // Prevent path traversal
    .substring(0, 255); // Limit length
}

function validateRedirect(url) {
  const allowedDomains = ['example.com', 'app.example.com'];
  
  try {
    const urlObj = new URL(url, window.location.origin);
    return allowedDomains.includes(urlObj.hostname);
  } catch {
    return false;
  }
}

function escapeHtml(text) {
  const map = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#x27;',
    '/': '&#x2F;'
  };
  
  return text.replace(/[&<>"'\/]/g, (char) => map[char]);
}

// React Hook Form with Zod
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';

function SignupForm() {
  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: zodResolver(userSchema)
  });
  
  const onSubmit = (data) => {
    console.log('Validated data:', data);
  };
  
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('email')} />
      {errors.email && <span>{errors.email.message}</span>}
    </form>
  );
}

Validation Checklist

Common Validation Rules

  • Email: RFC 5322 compliant regex
  • Password: 8+ chars, mixed case, numbers
  • URL: Valid protocol, no javascript:
  • Phone: Country-specific format
  • Credit Card: Luhn algorithm
  • Username: Alphanumeric, 3-20 chars
Defense in Depth: Validate on both client and server. Client validation is for UX, server validation is for security.

5. HTTPS TLS Certificate Pinning

Security Measure Implementation Purpose Use Case
HTTPS Enforcement Redirect HTTP to HTTPS, HSTS header Encrypt all traffic, prevent MITM attacks All production websites, mandatory for authentication
HSTS - HTTP Strict Transport Security Strict-Transport-Security: max-age=31536000 Force HTTPS for specified duration Prevent SSL stripping attacks, browser enforcement
Certificate Pinning Pin specific certificate or public key Prevent rogue CA certificates, targeted attacks Mobile apps, high-security APIs
TLS 1.3 Enable latest TLS protocol Faster handshake, stronger encryption Modern browsers, improved performance and security
Certificate Transparency Monitor CT logs for certificates Detect unauthorized certificate issuance Large organizations, prevent CA compromise

Example: HTTPS and TLS Configuration

// HSTS Header (Backend)
// Express.js
app.use((req, res, next) => {
  res.setHeader(
    'Strict-Transport-Security',
    'max-age=31536000; includeSubDomains; preload'
  );
  next();
});

// Helmet.js (Express security headers)
const helmet = require('helmet');

app.use(helmet({
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
  }
}));

// Nginx HTTPS redirect
server {
  listen 80;
  server_name example.com;
  return 301 https://$server_name$request_uri;
}

server {
  listen 443 ssl http2;
  server_name example.com;
  
  ssl_certificate /path/to/cert.pem;
  ssl_certificate_key /path/to/key.pem;
  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers HIGH:!aNULL:!MD5;
  ssl_prefer_server_ciphers on;
  
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
}

// Next.js HTTPS enforcement
// next.config.js
module.exports = {
  async redirects() {
    return [
      {
        source: '/:path*',
        has: [{ type: 'header', key: 'x-forwarded-proto', value: 'http' }],
        destination: 'https://example.com/:path*',
        permanent: true
      }
    ];
  }
};

// Certificate Pinning (Mobile - React Native)
// iOS Info.plist
<key>NSAppTransportSecurity</key>
<dict>
  <key>NSPinnedDomains</key>
  <dict>
    <key>api.example.com</key>
    <dict>
      <key>NSIncludesSubdomains</key>
      <true/>
      <key>NSPinnedCAIdentities</key>
      <array>
        <dict>
          <key>SPKI-SHA256-BASE64</key>
          <string>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</string>
        </dict>
      </array>
    </dict>
  </dict>
</dict>

// Public Key Pinning (Deprecated - use Certificate Transparency)
// Public-Key-Pins: pin-sha256="base64=="; max-age=5184000

// Detect HTTPS in JavaScript
if (window.location.protocol !== 'https:') {
  window.location.href = 'https:' + window.location.href.substring(window.location.protocol.length);
}

// Check for mixed content
const checkMixedContent = () => {
  const insecureResources = Array.from(document.querySelectorAll('img, script, link'))
    .filter(el => {
      const src = el.src || el.href;
      return src && src.startsWith('http://');
    });
  
  if (insecureResources.length > 0) {
    console.warn('Mixed content detected:', insecureResources);
  }
};

// Let's Encrypt SSL Certificate (Free)
// Certbot installation
sudo certbot --nginx -d example.com -d www.example.com

// Auto-renewal
sudo certbot renew --dry-run

// Cloudflare SSL modes
// Flexible: Client → Cloudflare (HTTPS), Cloudflare → Origin (HTTP)
// Full: Both encrypted, but origin certificate not verified
// Full (Strict): Both encrypted, origin certificate verified
// Recommended: Full (Strict)

// TLS 1.3 Configuration
ssl_protocols TLSv1.3;
ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384';

// CSP upgrade-insecure-requests
Content-Security-Policy: upgrade-insecure-requests;

TLS Best Practices

Certificate Authorities

  • Let's Encrypt: Free, automated, 90-day certs
  • DigiCert: Commercial, EV certificates
  • Cloudflare: Free SSL with CDN
  • AWS Certificate Manager: Free for AWS resources
  • Self-signed: Development only, not trusted
HSTS Preload: Submit domain to hstspreload.org for browser preload list. Browsers will always use HTTPS, even on first visit.

6. Subresource Integrity SRI Verification

Feature Implementation Purpose Browser Support
SRI - Subresource Integrity integrity="sha384-hash" Verify CDN resources haven't been tampered with 95%+ modern browsers
crossorigin attribute crossorigin="anonymous" Enable CORS for integrity checking Required with SRI for cross-origin resources
Hash Algorithms sha256, sha384, sha512 Cryptographic hash of resource content Use sha384 or sha512 for best security
Fallback Scripts Load local copy if CDN fails Reliability when CDN is down or compromised Manual implementation with onerror handler

Example: SRI Implementation

// SRI for CDN resources
<script
  src="https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js"
  integrity="sha384-KyZXEAg3QhqLMpG8r+Knujsl5+5hb7iegSk7F9+bL5wh8gL3LmqTLw3yPY4u6Rq6"
  crossorigin="anonymous"
></script>

// Multiple hashes (fallback algorithms)
<script
  src="https://code.jquery.com/jquery-3.6.0.min.js"
  integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4= sha512-hash"
  crossorigin="anonymous"
></script>

// CSS with SRI
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/bootstrap@5/dist/css/bootstrap.min.css"
  integrity="sha384-hash"
  crossorigin="anonymous"
/>

// Generate SRI hash
// Using OpenSSL
openssl dgst -sha384 -binary FILENAME.js | openssl base64 -A

// Using Node.js
const crypto = require('crypto');
const fs = require('fs');

function generateSRI(filePath) {
  const content = fs.readFileSync(filePath);
  const hash = crypto.createHash('sha384').update(content).digest('base64');
  return `sha384-${hash}`;
}

const sri = generateSRI('./script.js');
console.log(`integrity="${sri}"`);

// Using SRI online tools
// https://www.srihash.org/

// Webpack SRI Plugin
const SriPlugin = require('webpack-subresource-integrity');

module.exports = {
  output: {
    crossOriginLoading: 'anonymous'
  },
  plugins: [
    new SriPlugin({
      hashFuncNames: ['sha256', 'sha384'],
      enabled: process.env.NODE_ENV === 'production'
    })
  ]
};

// Next.js with SRI
// next.config.js
module.exports = {
  experimental: {
    sri: {
      algorithm: 'sha384'
    }
  }
};

// Fallback for CDN failure
<script
  src="https://cdn.example.com/library.js"
  integrity="sha384-hash"
  crossorigin="anonymous"
  onerror="loadFallback()"
></script>

<script>
function loadFallback() {
  const script = document.createElement('script');
  script.src = '/local/library.js'; // Local fallback
  document.head.appendChild(script);
}
</script>

// React CDN with fallback
<script
  src="https://unpkg.com/react@18/umd/react.production.min.js"
  integrity="sha384-hash"
  crossorigin="anonymous"
></script>
<script>
  if (typeof React === 'undefined') {
    document.write('<script src="/vendor/react.js"><\/script>');
  }
</script>

// Automated SRI in build process
// package.json script
{
  "scripts": {
    "sri": "node scripts/generate-sri.js"
  }
}

// scripts/generate-sri.js
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');

const distDir = path.join(__dirname, '../dist');
const files = fs.readdirSync(distDir);

files.forEach((file) => {
  if (file.endsWith('.js') || file.endsWith('.css')) {
    const filePath = path.join(distDir, file);
    const content = fs.readFileSync(filePath);
    const hash = crypto.createHash('sha384').update(content).digest('base64');
    
    console.log(`${file}: sha384-${hash}`);
  }
});

SRI Benefits

  • Prevents CDN tampering/injection
  • Detects modified third-party scripts
  • Protects against MITM attacks on CDN
  • Ensures resource authenticity
  • No performance overhead
  • Browser enforced validation

When to Use SRI

Resource Type Use SRI?
Public CDN scripts ✅ Always
Third-party libraries ✅ Always
Same-origin assets ❌ Optional
Dynamic content ❌ Not possible

Frontend Security Summary

  • XSS Prevention: Sanitize input with DOMPurify, use CSP, avoid innerHTML
  • CSRF Protection: HttpOnly cookies with SameSite=Strict, CSRF tokens
  • Token Storage: HttpOnly cookies (best), never localStorage for auth tokens
  • HTTPS: Enforce HTTPS, HSTS preload, TLS 1.3, Let's Encrypt certificates
  • CSP: Implement strict Content Security Policy with nonces
  • SRI: Verify CDN resources with integrity hashes
  • Validation: Validate client and server, never trust user input
Security is Layered: No single technique is foolproof. Implement multiple security layers: CSP + HttpOnly cookies + input validation + HTTPS + SRI for comprehensive protection.