Backend

CSRF vs XSS: Key Differences for Backend Developers

Compare CSRF and XSS attacks for backend developers, including how they work, why cookies and tokens matter, and practical defenses.

CSRF and XSS are both web security risks, but they attack different assumptions.

Cross-site scripting (XSS) tricks a trusted site into running attacker-controlled JavaScript in a user’s browser. Cross-site request forgery (CSRF) tricks a user’s browser into sending an unwanted request to a site where the user is already authenticated.

The short version:

  • XSS abuses trust in content rendered by your application.
  • CSRF abuses trust in automatically attached browser credentials, especially cookies.
  • XSS is usually defended with output encoding, sanitization, Content Security Policy, and safe rendering patterns.
  • CSRF is usually defended with SameSite cookies, CSRF tokens, origin checks, and careful request design.

Why the difference matters

Backend developers often discuss CSRF and XSS together because both affect authentication flows, cookies, sessions, forms, and APIs. But the fixes are not interchangeable.

If a page has an XSS vulnerability, an attacker may be able to run JavaScript in the context of your application. That can expose user data, perform actions as the user, or read tokens stored in JavaScript-accessible storage.

If an endpoint has a CSRF vulnerability, an attacker may be able to cause a logged-in user’s browser to submit a state-changing request. The attacker does not need to read the response for the action to matter.

CSRF vs XSS comparison

AreaCSRFXSS
Full nameCross-site request forgeryCross-site scripting
Main problemBrowser sends an unwanted authenticated requestBrowser runs untrusted script in a trusted page
Common targetState-changing endpointsPages that render user-controlled content
Common credential riskCookies attached automaticallyTokens, cookies, DOM data, and page state exposed to script
Typical defenseSameSite cookies, CSRF tokens, origin checksOutput encoding, sanitization, CSP, safe templates
Can attacker read response?Usually noOften yes, depending on the bug
Most relevant toForms, cookie sessions, browser requestsRendering, HTML, templates, rich text, user input

What is XSS?

XSS happens when an application renders untrusted data as executable content. This can happen through unsafe HTML rendering, vulnerable markdown or rich-text handling, unsafe template usage, or client-side code that inserts untrusted strings into the DOM.

For example, a comments feature, profile field, search page, or admin dashboard can become risky if user-controlled content is treated as trusted markup.

XSS is dangerous because the script runs in the user’s browser as if it belongs to your site. Depending on the page and browser controls, it may be able to read page data, call your APIs, modify forms, or interact with credentials available to JavaScript.

What is CSRF?

CSRF happens when another site causes a user’s browser to send a request to your application while the user is already authenticated there.

This usually matters most for cookie-based authentication because browsers attach matching cookies automatically. If a user is logged in to your app and an endpoint accepts a state-changing request without additional checks, another site may be able to trigger that request.

CSRF does not usually let the attacker read the response. The risk is that the action still happens: changing an email address, submitting a form, updating settings, or starting a transaction.

Cookies, bearer tokens, and browser behavior

The CSRF risk changes depending on how credentials are sent.

Cookie-based sessions are convenient, but cookies are attached automatically by the browser. That is why CSRF defenses matter for state-changing requests that rely on cookies.

Bearer tokens sent in an Authorization header are not automatically added by the browser to cross-site requests. That reduces classic CSRF risk, but it introduces a different question: where is the token stored, and can JavaScript read it?

If a bearer token is stored in localStorage, an XSS vulnerability can expose it. If a session token is stored in an HttpOnly cookie, JavaScript cannot read the cookie directly, but CSRF controls still matter for cookie-authenticated endpoints.

There is no perfect storage choice. Backend teams need to combine storage decisions with XSS prevention, CSRF prevention, short lifetimes, revocation, TLS, and careful logging.

How to defend against XSS

Start with safe rendering defaults. Use framework escaping, avoid unsafe HTML insertion, and treat user-provided strings as data rather than markup.

Use output encoding appropriate to the context: HTML text, HTML attributes, URLs, JavaScript strings, and CSS all have different rules.

For user-generated rich text or markdown, use a trusted sanitizer and restrict allowed tags and attributes. Do not rely on a simple blocklist.

Content Security Policy can reduce the blast radius of some XSS bugs, but it should be a defense layer, not the only protection.

Also avoid storing long-lived secrets in JavaScript-accessible storage when possible. If an XSS bug exists, anything readable by JavaScript should be treated as exposed.

How to defend against CSRF

For cookie-authenticated applications, use SameSite=Lax or SameSite=Strict cookies where the product flow allows it. Also use Secure in production so cookies are only sent over HTTPS.

Use CSRF tokens for state-changing form submissions and API requests that depend on cookies. The token should be unpredictable and tied to the user’s session or request context.

Validate Origin or Referer headers for sensitive browser requests. These checks are not a full replacement for CSRF tokens in every app, but they add useful protection.

Keep state-changing operations away from GET requests. Use POST, PUT, PATCH, or DELETE with appropriate validation and authorization checks.

Require re-authentication or stronger verification for highly sensitive changes, such as changing email, password, payout settings, or security settings.

How SameSite helps

The SameSite cookie attribute tells the browser when to send cookies with cross-site requests.

SameSite=Strict is the strongest common option, but it can break some legitimate flows where users navigate from another site.

SameSite=Lax is often a practical default for web apps because it blocks many cross-site subrequests while allowing normal top-level navigation.

SameSite=None allows cross-site cookie sending, but it requires Secure and should be used intentionally. It may be needed for embedded apps, third-party login flows, or cross-site integrations.

SameSite helps, but backend systems should still validate sensitive state changes carefully.

Where JWT storage fits

JWT vs session is a separate architecture decision from CSRF vs XSS, but the topics overlap.

A JWT in an Authorization header can reduce classic CSRF exposure because the browser does not attach that header automatically. But if the JWT is stored in JavaScript-accessible storage, XSS can become more damaging.

A session cookie can avoid exposing the raw session secret to JavaScript when it is HttpOnly, but cookie-based authentication needs CSRF-aware endpoint design.

This is why many production systems use short-lived access tokens, refresh token rotation, server-side session metadata, strong cookie attributes, and layered browser defenses together.

Common mistakes

The first mistake is treating CSRF and XSS as the same issue. They require different controls.

The second mistake is assuming JSON APIs are immune to CSRF. The real question is whether the browser can send authenticated state-changing requests that the server accepts.

The third mistake is storing long-lived tokens in JavaScript-accessible storage and assuming encryption or token signing solves XSS exposure. A signed token can still be stolen and used.

The fourth mistake is relying only on client-side validation. Security checks must happen on the server.

The fifth mistake is using GET requests for actions that change server state.

Practical backend recommendation

For a traditional web app with server-rendered pages and cookies:

  • Use HttpOnly, Secure, and SameSite cookies.
  • Add CSRF tokens to state-changing requests.
  • Escape output by default.
  • Sanitize any user-generated rich content.
  • Avoid unsafe HTML injection.
  • Keep sensitive actions behind fresh authorization checks.

For a single-page app or API-backed frontend:

  • Prefer short-lived access tokens.
  • Treat token storage as a serious design decision.
  • Rotate refresh tokens where possible.
  • Use safe rendering and XSS prevention consistently.
  • Validate CORS, origins, content types, and authorization server-side.

The practical goal is not choosing one magic security feature. It is reducing the number of assumptions an attacker can abuse.

Read JWT vs Session Authentication for the architecture tradeoff behind cookie sessions and stateless tokens. Then read Access Token vs Refresh Token and OAuth 2.0 Explained for Backend Developers for token lifecycle and authorization flow context.

For API design decisions around authentication failures, see REST API Status Codes Explained. You can also browse the Authentication topic cluster for the full learning path.