π
Advanced
6 min readWeb Security
XSS, CSRF, authentication, and security best practices
Frontend security questions test whether you understand the browser's security model and can identify and prevent common vulnerabilities. Many security bugs originate in the client β XSS alone is responsible for billions in fraud and data breaches annually.
Cross-Site Scripting (XSS)
XSS occurs when untrusted data is rendered as HTML without sanitisation, allowing attackers to inject and execute arbitrary JavaScript.
// Vulnerable β injecting user input as HTML
el.innerHTML = userInput; // NEVER do this
// Safe alternatives
el.textContent = userInput; // text only β no HTML parsing
el.innerText = userInput; // similar to textContent
// When you must render HTML (e.g. rich text): sanitise first
import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(userContent, { ALLOWED_TAGS: ['b', 'i', 'a'] });
// Content Security Policy β defence in depth
// Prevents execution of injected scripts even if XSS occurs
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' 'nonce-RANDOM'; style-src 'self'">
Cross-Site Request Forgery (CSRF)
CSRF tricks an authenticated user's browser into making an unintended request to a site where they are logged in.
// Prevention: SameSite cookies
Set-Cookie: session=abc; SameSite=Strict; Secure; HttpOnly
// CSRF token for forms
<form method="post">
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
</form>
// APIs: use Authorization header (not cookies) β not vulnerable to CSRF
fetch('/api/action', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + token },
});
Authentication Best Practices
// Store JWTs in memory (not localStorage) to prevent XSS exfiltration
let accessToken = null; // in-memory only
// Use httpOnly cookies for refresh tokens β JS cannot read them
// Set-Cookie: refresh_token=...; HttpOnly; Secure; SameSite=Strict; Path=/api/refresh
// Auto-refresh pattern
async function authFetch(url, options) {
if (isTokenExpired(accessToken)) {
accessToken = await refreshAccessToken(); // hits /api/refresh with httpOnly cookie
}
return fetch(url, {
...options,
headers: { ...options?.headers, Authorization: 'Bearer ' + accessToken },
});
}
Dependency Security
# Audit npm dependencies
npm audit
npm audit fix
# Check for known vulnerabilities
npx snyk test
# Lock your dependency tree
# Commit package-lock.json (npm) or yarn.lock
# Use exact versions for critical dependencies
Security Headers
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-xyz'
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), camera=(), microphone=()
OWASP Top 10 for Frontend
- A03 β Injection: XSS, SQL injection via APIs β sanitise all output
- A01 β Broken Access Control: hiding UI β preventing access β enforce server-side
- A02 β Cryptographic Failures: never store secrets in frontend code or localStorage
- A07 β Identification and Authentication: short-lived tokens, MFA, secure cookie flags
- A08 β Software Integrity Failures: use
integrityattribute on third-party scripts