How to prevent XSS attacks in JavaScript
Cross-Site Scripting (XSS) attacks are one of the most common web security vulnerabilities, allowing attackers to inject malicious scripts into web pages viewed by other users. With over 25 years of experience in software development and as the creator of CoreUI, a widely used open-source UI library, I’ve implemented XSS prevention measures in countless production applications. From my expertise, the most effective approach is to sanitize all user input and use safe DOM manipulation methods that prevent script execution. This method is reliable, widely supported, and should be your first line of defense against XSS attacks.
Use textContent instead of innerHTML and sanitize user input before rendering to prevent XSS attacks.
const userInput = '<script>alert("XSS")</script>'
const container = document.getElementById('content')
container.textContent = userInput
How It Works
The textContent property sets the text content of an element without parsing HTML, which means any HTML tags in the string are treated as plain text rather than executed. In the example above, the malicious script tag is displayed as text instead of being executed. This is in contrast to innerHTML, which would parse and execute the script.
Sanitizing User Input
For cases where you need to allow some HTML but prevent malicious scripts, use a sanitization library:
import DOMPurify from 'dompurify'
const userInput = '<p>Safe content</p><script>alert("XSS")</script>'
const clean = DOMPurify.sanitize(userInput)
document.getElementById('content').innerHTML = clean
Here DOMPurify removes the dangerous <script> tag while preserving the safe <p> tag. The sanitized output will be <p>Safe content</p> with the script completely removed.
Content Security Policy (CSP)
Implement a Content Security Policy header to add an extra layer of protection:
// Server-side (Express example)
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; script-src 'self'"
)
next()
})
This CSP header restricts the browser to only execute scripts from your own domain, blocking any inline scripts or scripts from external sources. Even if an attacker manages to inject a script tag, the browser will refuse to execute it.
Escaping HTML Entities
When displaying user input in HTML attributes or text, escape special characters:
const escapeHtml = (text) => {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/'
}
return text.replace(/[&<>"'/]/g, (char) => map[char])
}
const userInput = '<img src=x onerror=alert("XSS")>'
const safe = escapeHtml(userInput)
document.getElementById('content').textContent = safe
This function converts dangerous characters into their HTML entity equivalents. The < becomes <, the > becomes >, and so on, preventing any HTML from being interpreted as code.
Validating User Input
Always validate and filter user input on both client and server side:
const isValidEmail = (email) => {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return regex.test(email)
}
const userEmail = document.getElementById('email').value
if (!isValidEmail(userEmail)) {
throw new Error('Invalid email format')
}
This validation ensures that the email input matches the expected format, rejecting any input that contains unexpected characters or patterns that could be used for XSS attacks.
Using Modern Framework Features
Modern frameworks like React provide built-in XSS protection:
const UserComment = ({ comment }) => {
return <div>{comment}</div>
}
React automatically escapes any values embedded in JSX, so the comment variable will be rendered as text even if it contains HTML or script tags. To render raw HTML in React, you would need to explicitly use dangerouslySetInnerHTML, which serves as a warning that you’re bypassing the built-in protection.
Securing Event Handlers
Never use user input directly in event handlers:
// ❌ Dangerous
element.setAttribute('onclick', userInput)
// ✅ Safe
element.addEventListener('click', () => {
console.log('Safe handler')
})
The setAttribute approach with user input can allow attackers to inject malicious JavaScript code. Using addEventListener keeps the code separate from the data, making it impossible for user input to be interpreted as code.
Best Practice Note
This is the same security-first approach we use in CoreUI components to protect against XSS attacks and ensure that user data is always handled safely. For production applications, consider using a combination of input validation, output encoding, CSP headers, and sanitization libraries. Security is layered - no single technique is enough on its own. Always validate on the server side as well, since client-side validation can be bypassed. For input validation, check our guide on how to validate email in JavaScript for related validation techniques.



