Using express-rate-limit in Node.js:
express-rate-limit is a middleware package for Express applications that provides a straightforward way to set rate limits on your API endpoints.
// Define rate limit
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: "Too many requests from this IP, please try again later.",
});
// Apply the rate limit to all requests
app.use(limiter);
It’s not ideal for handling high volumes of traffic, as the rate limiting logic is processed by the Node.js server itself, which can still be vulnerable to high traffic.
Using Rate Limiting at the Nginx Server Level:
Nginx is designed to handle a high number of requests efficiently, and performing rate limiting directly at the web server level reduces the load on the application server (Node.js). This keeps your backend resources focused on serving legitimate requests.
http {
# Define a limit request zone
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
server {
listen 80;
location /api/ {
# Apply rate limiting
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://your-node-app;
}
}
} 2. JWT Blacklisting:
JWTs cannot be invalidated by default because they are stateless. This means:
If a token is compromised or if a user logs out, there’s no built-in way to invalidate the token.
Until the token expires, the holder of the token can access the application’s resources.
To overcome this, we use JWT blacklisting. Blacklisting is especially important for refresh tokens, as they are long-lived and allow users to get new access tokens.
When a user requests a new access token, generate a new refresh token and save it in the database or Redis with a reference to the user. Invalidate the old refresh token by removing it from the database.
async function refreshTokens(oldRefreshToken) {
const decoded = verifyRefreshToken(oldRefreshToken);
// Check if the token is still valid in the database
const storedToken = await db.getRefreshToken(decoded.jti);
if (!storedToken) return res.status(401).send("Invalid refresh token");
// Generate a new refresh token and invalidate the old one
const newRefreshToken = generateRefreshToken(decoded.userId);
await db.saveRefreshToken(newRefreshToken);
await db.deleteRefreshToken(decoded.jti);
return newRefreshToken;
} Only one valid refresh token exists at any given time for a user. If a user tries to use an old refresh token, it will no longer be in the database or cache, so it will be rejected.
3. Preventing XSS in Node.js Applications
Sanitize and Validate User Inputs
const { body, validationResult } = require('express-validator');
app.post('/submit-comment', [
body('comment').isString().trim().escape(), // Sanitize input
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Process comment here
}); Escape Output Properly
Use Content Security Policy (CSP) Headers
const helmet = require('helmet');
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", 'https://trusted-scripts.com'], // Allow only trusted sources
objectSrc: ["'none'"], // Prevent plugins like Flash
upgradeInsecureRequests: [],
},
})); X-XSS-Protection Header
app.use(helmet.xssFilter());
4. Implement Secure Session Management
Set Secure Cookies
Cookies are often used to store session IDs, so it’s important to configure them securely:
const session = require('express-session');
// Generating a secret using Node's crypto module
const crypto = require('crypto');
const sessionSecret = crypto.randomBytes(64).toString('hex');
app.use(session({
secret: sessionSecret,
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true, // Prevents JavaScript access to cookies
secure: true, // Ensures cookies are only sent over HTTPS (useful in production)
sameSite: 'lax' // Mitigates CSRF attacks by restricting cross-site requests
}
})); Use Short Session Expiration and Automatic Session Renewal
Sessions that last too long increase the risk of session hijacking if an attacker gains access to a session token. Implementing session expiration and automatic renewal minimizes this risk.
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: true,
cookie: {
maxAge: 30 * 60 * 1000, // 30 minutes
}
}));
// Middleware to renew session expiration on each request
app.use((req, res, next) => {
if (req.session) {
req.session._garbage = Date();
req.session.touch();
}
next();
}); 5. Prevent HTTP parameter pollution
To prevent HTTP Parameter Pollution, you can use the hpp middleware in your Node.js application. The hpp library (short for HTTP Parameter Pollution) is a simple, effective tool for handling duplicate parameters. It ensures that only the first occurrence of a parameter is accepted, discarding duplicates and eliminating potential issues.
const express = require('express');
const hpp = require('hpp');
const app = express();
// Apply hpp middleware
app.use(hpp());
app.get('/search', (req, res) => {
// Only the first instance of each query parameter is used
console.log(req.query); // { category: 'electronics' }
res.send('Search results');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
}); In this example, if a request like https://example.com/search?category=electronics&category=clothing is made, the hpp middleware will ensure only the first category parameter is considered (electronics), and the duplicate is ignored.
Securing a Node.js application requires a proactive approach across various layers of your stack. By implementing best practices like rate limiting, JWT blacklisting, secure session management, and protections against XSS and HTTP parameter pollution, you can greatly reduce the risk of common vulnerabilities. Prioritizing security not only protects your data but strengthens the overall resilience of your application.
Thank you for being a part of the In Plain English community! Before you go:
<hr><p>5 Essential Node.js Security Best Practices to Keep Your Application Safe was originally published in JavaScript in Plain English on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>