authentication, authorization, validation, rate limiting, CORS, headers, logging, database safety, deployment
Complete guide to protect Node.js API
Main security layers:
Client
↓
CORS
↓
Rate Limit
↓
Helmet Security Headers
↓
Authentication
↓
Authorization
↓
Input Validation
↓
Controller
↓
Database Protection
Express officially recommends security headers with helmet, safe cookies, dependency updates, rate limiting, and avoiding unsafe defaults. OWASP also strongly recommends allow-list input validation to prevent injection attacks.
1. Install security packages
npm install helmet cors express-rate-limit dotenv bcryptjs jsonwebtoken express-validator mongoose mongo-sanitize hpp morgan
2. Basic secure Express setup
const express = require("express");
const cors = require("cors");
const helmet = require("helmet");
const rateLimit = require("express-rate-limit");
const mongoSanitize = require("mongo-sanitize");
const hpp = require("hpp");
const morgan = require("morgan");
const app = express();
app.use(helmet());
app.use(cors({
origin: [
"https://cybotrix.com",
"https://admin.cybotrix.com",
"http://localhost:5173"
],
methods: ["GET", "POST", "PUT", "DELETE"],
allowedHeaders: ["Content-Type", "Authorization"]
}));
app.use(express.json({ limit: "10kb" }));
app.use((req, res, next) => {
req.body = mongoSanitize(req.body);
req.params = mongoSanitize(req.params);
req.query = mongoSanitize(req.query);
next();
});
app.use(hpp());
app.use(morgan("combined"));
CORS only controls whether browsers can read responses; tools like Postman or server-to-server requests do not depend on browser CORS enforcement.
3. Rate limiting
Use this to stop repeated requests, brute-force login attempts, and API abuse. express-rate-limit is designed for limiting repeated requests to public APIs and sensitive endpoints like password reset.
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: {
success: false,
message: "Too many requests. Please try again later."
}
});
app.use("/api", apiLimiter);
For login route:
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: {
success: false,
message: "Too many login attempts. Try again later."
}
});
app.use("/api/auth/login", loginLimiter);
4. Password hashing
Never store plain passwords.
const bcrypt = require("bcryptjs");
const hashedPassword = await bcrypt.hash(password, 10);
const isMatch = await bcrypt.compare(password, user.password);
5. JWT authentication
Login token generate
const jwt = require("jsonwebtoken");
const token = jwt.sign(
{
id: user._id,
role: user.role
},
process.env.JWT_SECRET,
{
expiresIn: "1d"
}
);
Auth middleware
const jwt = require("jsonwebtoken");
const protect = async (req, res, next) => {
try {
let token = req.headers.authorization;
if (!token || !token.startsWith("Bearer ")) {
return res.status(401).json({
success: false,
message: "Unauthorized. Token missing."
});
}
token = token.split(" ")[1];
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({
success: false,
message: "Invalid or expired token."
});
}
};
module.exports = protect;
6. Role-based authorization
Authentication means who you are. Authorization means what you are allowed to do. OWASP recommends authorization checks based on business rules and role/permission boundaries.
const allowRoles = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({
success: false,
message: "Access denied."
});
}
next();
};
};
module.exports = allowRoles;
Use:
router.delete(
"/users/:id",
protect,
allowRoles("admin"),
deleteUser
);
7. Input validation
const { body, validationResult } = require("express-validator");
const validateUser = [
body("name")
.trim()
.notEmpty()
.withMessage("Name is required"),
body("email")
.isEmail()
.withMessage("Valid email is required"),
body("password")
.isLength({ min: 6 })
.withMessage("Password must be minimum 6 characters"),
(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}
next();
}
];
module.exports = validateUser;
8. Protected route example
const express = require("express");
const router = express.Router();
const protect = require("../middleware/protect");
const allowRoles = require("../middleware/allowRoles");
router.get("/profile", protect, async (req, res) => {
res.json({
success: true,
user: req.user
});
});
router.get(
"/admin-dashboard",
protect,
allowRoles("admin"),
async (req, res) => {
res.json({
success: true,
message: "Admin dashboard data"
});
}
);
module.exports = router;
9. Environment variables
.env
PORT=5000
MONGO_URI=mongodb://127.0.0.1:27017/secure_api
JWT_SECRET=use_strong_secret_key_here
NODE_ENV=production
Never expose this file:
.env
node_modules
10. Secure MongoDB queries
Bad:
const user = await User.findOne(req.body);
Better:
const user = await User.findOne({
email: req.body.email
});
For update:
const allowedFields = ["name", "mobile", "city"];
const updateData = {};
allowedFields.forEach((field) => {
if (req.body[field]) {
updateData[field] = req.body[field];
}
});
await User.findByIdAndUpdate(req.params.id, updateData, {
new: true,
runValidators: true
});
11. Error handling middleware
app.use((err, req, res, next) => {
console.error(err);
res.status(err.statusCode || 500).json({
success: false,
message: err.message || "Server Error"
});
});
12. Final production checklist
Use HTTPS
Use Helmet
Use CORS with allowed domains only
Use rate limiting
Hash passwords with bcrypt
Use JWT expiry
Validate every input
Sanitize MongoDB input
Use role-based access
Hide .env file
Do not expose stack trace
Limit request body size
Update npm packages
Use logging
Use backup for database
Use firewall/security group
Best structure
project/
│
├── config/
│ └── db.js
│
├── controllers/
│ └── authController.js
│
├── middleware/
│ ├── protect.js
│ ├── allowRoles.js
│ └── errorHandler.js
│
├── models/
│ └── User.js
│
├── routes/
│ └── authRoutes.js
│
├── .env
├── server.js
└── package.json
For your admin panel/API, minimum protection should be:
Bearer Token
CORS only for admin domain
Role-based access
Rate limit login
Validate all form data
HTTPS compulsory
