Topic 17 of 55
API Security Best Practices
Overview
Security in Node.js APIs involves multiple layers: input validation, authentication, authorization, rate limiting, security headers, and preventing common vulnerabilities. Each layer is independently important.
Syntax
javascript
import helmet from "helmet";
import rateLimit from "express-rate-limit";
// 1. Security headers (Helmet)
app.use(helmet());
// 2. Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100,
message: { error: "Too many requests, please try again later" },
});
app.use("/api", limiter);
// Stricter limit for auth routes
const authLimiter = rateLimit({ windowMs: 60 * 1000, max: 5 });
app.use("/api/auth", authLimiter);
// 3. Limit payload size
app.use(express.json({ limit: "100kb" }));
// 4. CORS
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(","),
credentials: true,
}));Common Pitfalls
- Never store passwords in plaintext — use bcrypt with work factor >= 12 or Argon2.
- JWT secrets must be cryptographically random (at least 32 bytes) — do not use simple strings like 'secret'.
- Interview tip: OWASP Top 10 for APIs is the security checklist. The most common issues in Node.js: broken authentication, SQL/NoSQL injection, and mass assignment vulnerabilities.
Real-World Example
Security middleware and timing-safe comparison
example
javascript
import { timingSafeEqual } from "crypto";
// Prevent timing attacks on authentication
function safeCompare(a, b) {
const aBuf = Buffer.from(a);
const bBuf = Buffer.from(b);
if (aBuf.length !== bBuf.length) return false;
return timingSafeEqual(aBuf, bBuf);
}
// Prevent information disclosure in errors
app.use((err, req, res, next) => {
const isProd = process.env.NODE_ENV === "production";
res.status(err.status || 500).json({
error: {
message: isProd && !err.status ? "Internal Server Error" : err.message,
code: err.code,
// NEVER expose stack trace in production
},
});
});