Technology

Frontend Security: XSS, CSRF, and CSP Headers

Practical defense against common web vulnerabilities: input sanitization, CSRF tokens, and Content Security Policy.

All articles
TechnologyNexaEx TeamMay 15, 2026 8 min read
Frontend Security: XSS, CSRF, and CSP Headers

Frontend Security: Protecting Against Attacks

Frontend security is critical but often overlooked. XSS, CSRF, and other vulnerabilities can steal user data or compromise applications.

Cross-Site Scripting (XSS)

XSS is injecting malicious JavaScript into web pages.

Stored XSS: Attacker stores malicious code in database (via form input). When users load the page, code executes in their browser.

User submits comment: <script>alert('hacked')</script>
System stores it as-is.
Next user loads page → script executes in their browser.

Reflected XSS: Attacker tricks user into clicking malicious link.

Attacker sends: https://example.com/search?q=<script>stealCookie()</script>
User clicks link.
Server reflects query parameter in response → script executes.

Defense: Input Sanitization

Never trust user input. Sanitize everything:

// Bad: Raw HTML
const comment = userInput // <script>alert('xss')</script>
document.getElementById('comments').innerHTML = comment

// Good: Text-only
const comment = userInput
document.getElementById('comments').textContent = comment

// Good: Sanitized HTML (if HTML is needed)
import DOMPurify from 'dompurify'

const cleanComment = DOMPurify.sanitize(userInput, { ALLOWED_TAGS: ['b', 'i', 'u'] })
document.getElementById('comments').innerHTML = cleanComment

Defense: Context-Aware Encoding

Different contexts require different encoding:

// HTML context: Encode <, >, &
const text = userInput.replace(/[<>&]/g, (c) => ({
  '<': '&lt;',
  '>': '&gt;',
  '&': '&amp;',
}[c]))

// JavaScript context: Escape quotes and backslashes
const jsString = userInput.replace(/[\'"]/g, '\$&')

// URL context: Encode special characters
const url = encodeURIComponent(userInput)

// CSS context: Avoid user input in CSS (use classes instead)
// Bad: element.style.backgroundColor = userColor
// Good: element.className = userColor // Map to predefined CSS classes

Defense: Content Security Policy (CSP)

CSP restricts where scripts can load from:

<!-- Only allow scripts from same origin; block inline scripts -->
<meta http-equiv="Content-Security-Policy" content="script-src 'self'" />

<!-- Allow specific trusted domains -->
<meta http-equiv="Content-Security-Policy" content="script-src 'self' https://cdn.example.com" />

<!-- Strict: disallow inline scripts entirely -->
<meta http-equiv="Content-Security-Policy" content="script-src 'self'; default-src 'none'" />

Server can also set CSP headers:

Content-Security-Policy: script-src 'self' https://trusted-cdn.com; style-src 'self' https://fonts.googleapis.com; default-src 'none'

Cross-Site Request Forgery (CSRF)

CSRF tricks users into making unwanted requests on behalf of attackers.

User logged into bank.com.
Attacker tricks user into visiting malicious.com.
malicious.com has: <img src="bank.com/transfer?to=attacker&amount=1000" />
Browser makes request to bank.com with user's cookies.
Transfer succeeds without user consent.

Defense: CSRF Tokens

Server generates unique token for each user session. Requests must include the token.

// Server generates token
const token = generateToken() // Unique per session
res.cookie('csrf-token', token)
res.send(`<form><input type="hidden" name="csrf-token" value="${token}" /></form>`)

// Client includes token
const token = document.querySelector('input[name="csrf-token"]').value
fetch('/api/transfer', {
  method: 'POST',
  headers: { 'X-CSRF-Token': token },
  body: JSON.stringify({ to: 'recipient', amount: 1000 }),
})

// Server validates token
if (req.headers['x-csrf-token'] !== req.session.csrfToken) {
  res.status(403).send('CSRF token invalid')
}

Defense: SameSite Cookies

Set SameSite=Strict on cookies. Cookies sent only to same site, not cross-site requests.

Set-Cookie: sessionId=abc123; SameSite=Strict

Clickjacking

Attacker overlays invisible iframe on top of legitimate site, tricks users into clicking.

<!-- Attacker's page -->
<iframe src="bank.com" style="opacity: 0; position: absolute; top: 0; left: 0;"></iframe>
<!-- Visible button that aligns with hidden iframe's "Transfer" button -->
<button style="position: absolute; top: 100px; left: 100px;">Claim Prize!</button>

Defense: X-Frame-Options Header

X-Frame-Options: DENY        # Prevent framing entirely
X-Frame-Options: SAMEORIGIN  # Allow only same-origin framing
X-Frame-Options: ALLOW-FROM https://trusted.com # Allow specific origins

Dependency Vulnerabilities

Third-party libraries have vulnerabilities. Keep them updated:

npm audit          # Identify vulnerabilities
npm audit fix      # Auto-fix if possible
npm outdated       # Show out-of-date packages

Use lock files (package-lock.json) to ensure consistent versions across team.

Best Practices Checklist

  • Sanitize all user input
  • Never use innerHTML with user data
  • Use textContent or DOMPurify for HTML
  • Set CSP headers
  • Use CSRF tokens on state-changing requests
  • Set SameSite cookies
  • Use X-Frame-Options header
  • Keep dependencies updated
  • Audit regularly with OWASP guidelines
  • Use HTTPS everywhere
  • Implement subresource integrity (SRI) for CDN scripts

Real Example

A comment system vulnerable to XSS:

Attack:

User submits: <img src=x onerror="stealCookie()">
System stores and displays to others.

Fix:

const comment = DOMPurify.sanitize(userInput, { ALLOWED_TAGS: [] })
// Converts to: &lt;img src=x onerror="stealCookie()"&gt;
// Displays as text, doesn't execute.

Result: Safe, user intent preserved, attack prevented.

Frequently asked questions

Should I use innerHTML or textContent?

textContent for user-generated content (safe, no parsing). innerHTML only for trusted content or sanitized input via DOMPurify.

What's the difference between SameSite=Strict and SameSite=Lax?

Strict: Never send cookies cross-site. Lax: Send on same-site requests and top-level navigation. Strict is more secure.

Do I need CSP if I already sanitize input?

Yes. CSP is defense-in-depth. Even with sanitization, CSP blocks inline scripts and restricts trusted sources.

Let's build your next idea

One conversation to scope the work, meet the team, and get a proposal — usually within two business days.