Key Takeaways
- Cross-Site Scripting (XSS) remains the most common web vulnerability — always sanitize user input
- HTTPS is mandatory for all websites — HTTP sites are flagged as "Not Secure" by browsers
- Content Security Policy (CSP) headers prevent unauthorized script execution
- CSRF tokens protect state-changing operations from cross-site request forgery
- Never store passwords in plain text — use bcrypt with a cost factor of 12+
Table of Contents
Website security is not a feature you add after launch — it is a fundamental requirement baked into every layer of your application, from HTML markup to server configuration. A security breach can destroy user trust, expose sensitive data, trigger legal consequences, and damage your brand irreparably. Yet many developers treat security as an afterthought, assuming their site is "too small" to be targeted.
The reality is that attacks are automated. Bots scan millions of websites daily for known vulnerabilities. If your site has a contact form, a login system, or any user input, it is a target. In this guide, we cover the essential security practices every web developer must implement.
1. The Modern Threat Landscape
According to the OWASP Top 10 (2021), the most critical web application security risks include:
- Broken Access Control: Users accessing data or functions they should not
- Cryptographic Failures: Weak encryption, exposed sensitive data
- Injection: SQL injection, XSS, command injection
- Insecure Design: Architectural flaws that cannot be fixed with patches
- Security Misconfiguration: Default passwords, exposed error messages, unnecessary services
Understanding these categories helps prioritize where to focus your security efforts.
2. Cross-Site Scripting (XSS) Prevention
XSS attacks inject malicious JavaScript into your website, which then executes in other users' browsers. This can steal session cookies, redirect users to phishing sites, or modify page content.
Types of XSS
- Stored XSS: Malicious script is saved to your database (e.g., in a comment or user profile) and served to every visitor
- Reflected XSS: Malicious script is embedded in a URL parameter and reflected back in the page response
- DOM-based XSS: Malicious script manipulates the page's DOM through client-side JavaScript
Prevention Techniques
// 1. NEVER insert raw user input into the DOM
// Bad — direct XSS vulnerability:
element.innerHTML = userInput;
// Good — escape HTML entities:
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
element.innerHTML = escapeHtml(userInput);
// Better — use textContent for plain text (no HTML parsing):
element.textContent = userInput;
// 2. Server-side: Sanitize on output, not just input
// Use libraries like DOMPurify for HTML that must contain formatting:
const clean = DOMPurify.sanitize(userHTML, { ALLOWED_TAGS: ['b', 'i', 'em', 'strong'] });
3. CSRF Protection
Cross-Site Request Forgery (CSRF) tricks authenticated users into submitting unwanted requests. If a user is logged into your site and visits a malicious page, that page can submit forms to your site using the user's session cookies.
// Server-side CSRF protection with tokens
const crypto = require('crypto');
// Generate a unique token per session
function generateCSRFToken() {
return crypto.randomBytes(32).toString('hex');
}
// Middleware: Attach token to session and verify on POST requests
app.use((req, res, next) => {
if (req.method === 'GET') {
req.session.csrfToken = generateCSRFToken();
res.locals.csrfToken = req.session.csrfToken;
}
if (['POST', 'PUT', 'DELETE'].includes(req.method)) {
const token = req.body._csrf || req.headers['x-csrf-token'];
if (token !== req.session.csrfToken) {
return res.status(403).json({ error: 'Invalid CSRF token' });
}
}
next();
});
// Client-side: Include the token in every form
// <input type="hidden" name="_csrf" value="${csrfToken}">
4. HTTPS and SSL/TLS
HTTPS encrypts all data between the browser and your server, preventing man-in-the-middle attacks, cookie theft, and data interception. In 2026, HTTPS is not optional:
- Chrome marks HTTP sites as "Not Secure" with a prominent warning
- Google uses HTTPS as a ranking signal — HTTP sites rank lower
- Many modern browser APIs (geolocation, service workers, clipboard) require HTTPS
- Free SSL certificates are available from Let's Encrypt
// Express.js: Force HTTPS redirect
app.use((req, res, next) => {
if (req.headers['x-forwarded-proto'] !== 'https' && process.env.NODE_ENV === 'production') {
return res.redirect(301, 'https://' + req.hostname + req.url);
}
next();
});
// HSTS header: Tell browsers to always use HTTPS
app.use((req, res, next) => {
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
next();
});
5. Content Security Policy
Content Security Policy (CSP) is an HTTP header that tells the browser which sources of content are allowed to execute on your page. It is the most powerful defense against XSS attacks:
// Express.js CSP header
app.use((req, res, next) => {
res.setHeader('Content-Security-Policy', [
"default-src 'self'",
"script-src 'self' https://cdnjs.cloudflare.com https://challenges.cloudflare.com",
"style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://fonts.googleapis.com",
"font-src 'self' https://fonts.gstatic.com https://cdnjs.cloudflare.com",
"img-src 'self' data: https:",
"connect-src 'self' https://accounts.google.com",
"frame-src 'self' https://challenges.cloudflare.com",
"object-src 'none'",
"base-uri 'self'"
].join('; '));
next();
});
With CSP enabled, even if an attacker injects a <script> tag into your page, the browser will refuse to execute it because the script source is not in the CSP allowlist.
6. Secure Authentication
Password Hashing
const bcrypt = require('bcrypt');
// Hash password before storing (cost factor 12)
const hashPassword = async (plaintext) => {
return await bcrypt.hash(plaintext, 12);
};
// Verify password during login
const verifyPassword = async (plaintext, hash) => {
return await bcrypt.compare(plaintext, hash);
};
// NEVER store passwords in plain text
// NEVER use MD5, SHA1, or SHA256 for passwords — they are too fast
// bcrypt is intentionally slow, making brute-force attacks impractical
Session Security
// Secure session configuration
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true, // Prevents JavaScript access to cookies
secure: true, // Only sent over HTTPS
sameSite: 'lax', // Prevents CSRF via cross-site requests
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
}));
7. Security Headers Checklist
Every production website should include these HTTP security headers:
// Essential security headers
res.setHeader('X-Content-Type-Options', 'nosniff'); // Prevent MIME sniffing
res.setHeader('X-Frame-Options', 'SAMEORIGIN'); // Prevent clickjacking
res.setHeader('X-XSS-Protection', '1; mode=block'); // Legacy XSS filter
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
You can test your security headers using SecurityHeaders.com — aim for an A+ rating.
Conclusion
Website security is an ongoing discipline, not a one-time setup. The techniques covered in this guide — XSS prevention, CSRF protection, HTTPS enforcement, Content Security Policy, and secure authentication — form the baseline security posture every website must have. As new vulnerabilities are discovered, staying current with security best practices is essential.
At Renvima, security is integrated into every custom web application we build, from input sanitization to session management to security headers. We believe that users trust you with their data, and that trust must be earned through rigorous engineering.
Build secure websites from the ground up.
Security-first engineering is at the core of every Renvima project.
Browse Templates