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 = {
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return escapeChars[char];
});
}
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 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
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 = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/'
};
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>
);
}
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;
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}`);
}
});
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.