CSRF vs XSS: Understanding the Difference and How to Mitigate Both

Security & Architecture

Security vulnerabilities often sound similar until you understand exactly what each one abuses. CSRF and XSS are both classic web attacks, both involve browsers and servers, and both appear on every security checklist — but they exploit fundamentally different trust relationships and require different mitigations.

XSS: The Attacker Injects Code Into Your Site

Cross-Site Scripting (XSS) happens when an attacker manages to inject malicious JavaScript into a page that other users then load. The browser executes it because it looks like legitimate code from your site.

There are three variants:

Stored XSS — the malicious script is saved in your database (e.g. a comment field) and served to every user who visits that page.

Reflected XSS — the script is embedded in a URL. When a victim clicks the link, the server reflects the input back in the response without sanitising it.

DOM-based XSS — the attack happens entirely client-side. JavaScript on your page reads from a source like location.hash or document.referrer and writes it to the DOM without sanitisation.

// ❌ DOM-based XSS vulnerability
const name = new URLSearchParams(location.search).get('name')
document.getElementById('greeting').innerHTML = `Hello, ${name}!`
// If name = <script>stealCookies()</script> — you're compromised

The fix is to never use innerHTML with untrusted input. Use textContent instead, or a sanitisation library like DOMPurify:

// ✅ Safe
document.getElementById('greeting').textContent = `Hello, ${name}!`

// ✅ Safe with HTML allowed (e.g. rich text)
import DOMPurify from 'dompurify'
el.innerHTML = DOMPurify.sanitize(userInput)

What XSS Can Do

Once an attacker's script runs in your origin, it has full access to everything in that context: cookies (unless HttpOnly), localStorage, sessionStorage, the DOM, and the ability to make authenticated requests or exfiltrate data to an external server. XSS is game over for the affected session.

CSRF: The Attacker Exploits Your Browser's Cookies

Cross-Site Request Forgery (CSRF) is a different attack entirely. The attacker doesn't inject code into your site. Instead, they trick the victim's browser into sending a forged request to your site from a different origin — and the browser helpfully attaches the victim's cookies.

<!-- Attacker's page at evil.com -->
<img src="https://bank.com/transfer?to=attacker&amount=1000" />

When the victim loads this page, their browser fires a GET request to bank.com. Because the victim is logged in, their session cookie is included automatically. If bank.com performs state-changing operations on GET, the transfer goes through.

Modern browsers have reduced this risk significantly with SameSite cookies, but CSRF still matters for older browsers or cross-site flows that legitimately need cookies.

How to Defend Against CSRF

CSRF tokens are the classic defence. The server generates a random, unpredictable token per session (or per form), includes it in the HTML, and requires it on every state-changing request. An attacker on another origin can't read your page's content (same-origin policy blocks it), so they can't obtain the token.

<form method="POST" action="/transfer">
  <input type="hidden" name="_csrf" value="a3f9...token...d2c1">
  ...
</form>

SameSite cookies are the modern, lower-friction solution. Setting SameSite=Strict means the cookie is never sent on cross-site requests at all. SameSite=Lax (the browser default since ~2020) allows cookies on top-level navigations but blocks them on subresource requests from other origins.

Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnly

Origin / Referer header validation — check that the request's Origin or Referer header matches your domain. Not bulletproof on its own (some browsers omit headers), but a good second layer.

Side-by-Side Comparison

XSSCSRF
Attack vectorInjecting code into your siteForging requests from another site
Trust exploitedUser trusts your site's scriptsServer trusts the user's browser/cookies
Attacker needsA way to inject unsanitised inputA logged-in victim to visit attacker's page
Primary defencesInput sanitisation, CSPCSRF tokens, SameSite cookies
Cookie HttpOnly helps?Yes — prevents JS from reading cookiesNo — cookies are still sent automatically

Content Security Policy Reduces XSS Blast Radius

A Content Security Policy (CSP) header tells the browser which sources of scripts are trusted. Even if an attacker injects a <script> tag, it won't execute if the source isn't whitelisted:

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

CSP doesn't eliminate XSS (it's a mitigation, not a prevention) but it meaningfully reduces what an attacker can do with a successful injection.

Conclusion

XSS and CSRF target opposite ends of the trust model. XSS breaks the user's trust in your site's content by injecting code. CSRF breaks your server's trust in the user's browser by forging requests. Defending against both is non-negotiable: sanitise all output, use HttpOnly + SameSite cookies, issue CSRF tokens for state-changing endpoints, and add a Content Security Policy as a defence-in-depth layer.