Node.js Security Best Practices
/ 3 min read
Table of Contents
Node.js Security Best Practices
Securing a Node.js application involves protecting against injections, handling authentication securely, managing dependencies, and securing API endpoints.
This guide covers:
- Preventing SQL & NoSQL injections
- Securing authentication and session management
- Avoiding prototype pollution vulnerabilities
- Protecting API endpoints from abuse
1. Validate and Sanitize User Input
Unvalidated user input can lead to injections, XSS, and prototype pollution attacks.
Bad: Directly Using User Input in Queries
app.get("/user", async (req, res) => { const user = await db.query(`SELECT * FROM users WHERE id = ${req.query.id}`); res.json(user);});
Fix: Use Parameterized Queries
app.get("/user", async (req, res) => { const user = await db.query("SELECT \* FROM users WHERE id = $1", [ req.query.id, ]); res.json(user);});
Fix: Use Input Validation with Zod
import { z } from "zod";
const userSchema = z.object({ id: z.string().uuid(),});
const validatedData = userSchema.parse(req.query);
2. Prevent Prototype Pollution Attacks
Attackers can inject properties into JavaScript prototypes, causing unexpected behavior.
Vulnerable Code
const userPreferences = JSON.parse(req.body);Object.assign({}, userPreferences);
Fix: Use Object.create(null)
const safeObject = Object.create(null);Object.assign(safeObject, userPreferences);
3. Secure Authentication and Sessions
Bad: Storing JWT in Local Storage
localStorage.setItem("token", token);
Fix: Use Secure HTTP-Only Cookies
// Server-side (Express.js)app.post("/login", (req, res) => { res.cookie("auth_token", "secure-token", { httpOnly: true, secure: true, sameSite: "Strict", }); res.send("Logged in");});
Fix: Rotate JWTs to Prevent Token Replay
const refreshToken = jwt.sign({ userId }, secretKey, { expiresIn: "7d" });
4. Secure API Endpoints with Rate Limiting
Rate limiting prevents brute-force attacks and API abuse.
import rateLimit from "express-rate-limit";
const limiter = rateLimit({ windowMs: 15 _ 60 _ 1000, max: 100 });
app.use("/api/", limiter);
5. Prevent Directory Traversal Attacks
Allowing unsanitized file paths can let attackers access system files.
Vulnerable Code
app.get("/file", (req, res) => { res.sendFile(\_\_dirname + "/uploads/" + req.query.filename);});
Fix: Sanitize File Paths
import path from "path";
app.get("/file", (req, res) => { const safePath = path.join(\_\_dirname, "uploads", path.basename(req.query.filename)); res.sendFile(safePath);});
6. Keep Dependencies Updated and Secure
Vulnerable dependencies introduce security flaws.
Check for Security Issues
npm audit
Fix Vulnerabilities
npm audit fix --force
7. Set Secure HTTP Headers
Use Helmet.js to enforce secure HTTP headers.
import helmet from "helmet";app.use(helmet());
This prevents:
- Clickjacking attacks (
X-Frame-Options
) - Cross-site scripting (
X-XSS-Protection
) - Content sniffing attacks (
X-Content-Type-Options
)
8. Avoid Exposing Sensitive Data in Errors
Bad: Returning Detailed Error Messages
app.use((err, req, res, next) => { res.status(500).json({ error: err.message });});
Fix: Use Generic Error Responses
app.use((err, req, res, next) => { res.status(500).json({ error: "Internal Server Error" });});
Conclusion
- Validate all user input to prevent SQL & NoSQL injections.
- Prevent prototype pollution attacks by securing object assignments.
- Use HTTP-only cookies for authentication instead of storing JWTs in local storage.
- Secure API endpoints with rate limiting and proper authorization.
- Sanitize file paths to prevent directory traversal attacks.
- Keep dependencies updated and use
npm audit
to check for vulnerabilities.
Security is a continuous process — implement these safeguards to reduce risks and protect data.