Prevent XSS Attacks in Node.js
Cross-Site Scripting (XSS) remains one of the most prevalent security vulnerabilities in web applications, allowing attackers to inject malicious scripts into webpages viewed by other users. This article provides a direct, actionable guide on how to secure your Node.js applications against XSS attacks. You will learn the essential security practices to implement immediately, including input sanitization, output encoding, securing HTTP headers with Helmet, and protecting session cookies.
1. Sanitize and Validate User Input
Never trust user input. Attackers can submit malicious scripts through form fields, query parameters, or JSON payloads. To block these attempts, you must validate and sanitize all incoming data.
Use the express-validator library to validate that
inputs match expected formats (e.g., ensuring an email field actually
contains an email) and to strip out potentially dangerous
characters.
const { body, validationResult } = require('express-validator');
app.post('/submit', [
body('username').trim().escape(),
body('email').isEmail().normalizeEmail()
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Process safe data
});If your application must accept rich text (like HTML from a WYSIWYG
editor), use a dedicated library like sanitize-html to
whitelist only safe HTML tags and attributes, discarding any
<script> or onload attributes.
const sanitizeHtml = require('sanitize-html');
const cleanHTML = sanitizeHtml(dirtyHTML, {
allowedTags: [ 'b', 'i', 'em', 'strong', 'a' ],
allowedAttributes: {
'a': [ 'href' ]
}
});2. Use Context-Aware Output Encoding
XSS occurs when the browser interprets user-supplied data as
executable code. Output encoding converts special characters into their
safe HTML entity equivalents (e.g., converting < to
< and " to
").
If you are using modern frontend frameworks like React, Angular, or Vue, they perform automatic HTML escaping by default. If you are rendering templates on the server side using engines like EJS, Pug, or Handlebars, ensure you use the correct escaping syntax:
- EJS: Use
<%= username %>(escaped) instead of<%- username %>(raw HTML). - Handlebars: Use
{{username}}(escaped) instead of{{{username}}}(raw HTML).
3. Implement a Content Security Policy (CSP)
A Content Security Policy (CSP) is an HTTP header that restricts the resources (such as JavaScript, CSS, and Images) that the browser is allowed to load for a given page. A strong CSP can completely neutralize XSS attacks by preventing unauthorized inline scripts from executing.
The easiest way to implement CSP in a Node.js/Express application is
by using the helmet middleware.
const express = require('express');
const helmet = require('helmet');
const app = express();
// Use Helmet to set secure HTTP headers, including CSP
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://trustedscripts.com"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:"],
},
},
})
);4. Secure Session Cookies
Attackers often exploit XSS vulnerabilities to steal session tokens
stored in cookies, allowing them to hijack user accounts. You can
prevent JavaScript from reading your cookies by setting the
HttpOnly flag.
When configuring your session middleware (like
express-session), always apply the following security
flags:
- httpOnly: Prevents client-side scripts from
accessing the cookie via
document.cookie. - secure: Ensures the cookie is only sent over encrypted HTTPS connections.
- sameSite: Mitigates Cross-Site Request Forgery (CSRF) attacks.
const session = require('express-session');
app.use(session({
secret: 'your-secure-secret',
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
secure: true, // Requires HTTPS
sameSite: 'strict'
}
}));