Web Security Essentials: OWASP Top 10 for Node.js and React
Practical security — preventing SQL injection, XSS, broken authentication, and IDOR in Node.js applications.
Most web breaches exploit preventable vulnerabilities. This guide covers the OWASP Top 10 with practical code.
1. SQL Injection Prevention
// VULNERABLE
const users = await db.query(`SELECT * FROM users WHERE email = '${email}'`);
// SAFE — parameterized query
const users = await db.query("SELECT * FROM users WHERE email = $1", [email]);
// SAFE — ORM
const user = await prisma.user.findUnique({ where: { email } });
2. Secure Authentication
import bcrypt from "bcryptjs";
import { SignJWT } from "jose";
async function hashPassword(password: string) {
return bcrypt.hash(password, 12);
}
async function signToken(userId: string) {
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
return new SignJWT({ sub: userId })
.setProtectedHeader({ alg: "HS256" })
.setExpirationTime("15m")
.sign(secret);
}
3. XSS Prevention
// React auto-escapes JSX — safe by default
const Profile = ({ name }: { name: string }) => <h1>{name}</h1>;
// Sanitize rich text before rendering
import DOMPurify from "isomorphic-dompurify";
const SafeContent = ({ html }: { html: string }) => (
<div
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(html, {
ALLOWED_TAGS: ["p", "b", "i", "ul", "ol", "li", "a"],
}),
}}
/>
);
4. Access Control (IDOR Prevention)
// SAFE — verify resource ownership before returning data
app.get("/orders/:id", authenticate, async (req, res) => {
const order = await db.orders.findFirst({
where: { id: req.params.id, userId: req.user.id },
});
if (!order) return res.status(404).json({ error: "Not found" });
res.json(order);
});
5. Security Headers with Helmet
import helmet from "helmet";
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
},
},
hsts: { maxAge: 31536000, includeSubDomains: true },
}));
6. Rate Limiting
import rateLimit from "express-rate-limit";
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: "Too many login attempts.",
});
app.post("/auth/login", loginLimiter, loginHandler);
Security Checklist
- Parameterized queries for all SQL
- Passwords hashed with bcrypt (rounds >= 12)
- JWT access tokens expire < 1 hour
- Login rate-limited (5 attempts / 15 min)
- Security headers via Helmet
- CORS restricted to known origins
- All endpoints check authorization, not just authentication
- Rich text sanitized before rendering
- Secrets excluded from logs
Conclusion
Security built from day one. Ventra Rocket includes a security review in every project delivery, checking against the OWASP checklist.
Related Articles
Next.js App Router: Patterns for Production
A practical guide to building fast, scalable web apps with the Next.js App Router — server components, streaming, parallel routes, intercepting routes, and caching strategies.
10 TypeScript Patterns for Cleaner Code
A practical collection of 10 TypeScript patterns — from discriminated unions and template literal types to the satisfies operator — that make your codebase more type-safe and maintainable.
React State Management in 2026: Zustand, TanStack Query, and URL State
Choosing the right state management for React — TanStack Query for server state, Zustand for global UI state, URL state for filters, useState for local state.