← All posts
June 25, 2026 · Sécurité

Securing a Node.js/Express API: The 5 Golden Rules

Securing your API endpoints is crucial in production. Discover the five core best practices to shield your Node.js backend against common attacks.

Introduction

Node.js and Express power a significant portion of modern web applications, microservices, and REST APIs. Yet the ecosystem is frequently targeted by HTTP header fingerprinting, lack of input validation, denial-of-service (DoS) attacks, and NoSQL/SQL injection vulnerabilities.

Here are the 5 Golden Rules for securing a Node.js API in production, illustrated with concrete vulnerable code and full remediation examples.


Rule #1 — Secure HTTP Headers with Helmet

By default, an Express server exposes X-Powered-By: Express, immediately revealing the technology stack to automated vulnerability scanners.

❌ Vulnerable Default Express Server

const express = require('express');
const app = express();

// ❌ Exposes X-Powered-By: Express, no security headers applied
app.get('/api/data', (req, res) => {
  res.json({ status: "ok" });
});

app.listen(3000);

✅ Secured with Helmet

const express = require('express');
const helmet = require('helmet');
const app = express();

// ✅ Helmet removes X-Powered-By and injects 15 HTTP security headers:
// Strict-Transport-Security (HSTS), X-Frame-Options (Clickjacking), 
// X-Content-Type-Options, Content-Security-Policy, Referrer-Policy...
app.use(helmet());

app.get('/api/data', (req, res) => {
  res.json({ status: "ok" });
});

app.listen(3000);

Rule #2 — Strict Input Validation with Zod (Anti-Injection & Sanitization)

Never trust client-supplied data. req.body, req.query, and req.params must be explicitly validated and type-checked before processing.

❌ Vulnerable Code (NoSQL Injection / Arbitrary Payload)

// If a user sends { "email": { "$ne": null }, "password": { "$ne": null } }
// a Mongoose query will return the first user from the database without a valid password!
app.post('/api/login', async (req, res) => {
  const user = await db.user.findOne({ 
    email: req.body.email, 
    password: req.body.password 
  });
  res.json(user);
});

✅ Secured with Zod Validation

import { z } from 'zod';
import express from 'express';

const app = express();
app.use(express.json());

const LoginSchema = z.object({
  email: z.string().email("Invalid email format"),
  password: z.string().min(8, "Password must be at least 8 characters").max(100)
});

app.post('/api/login', async (req, res) => {
  const result = LoginSchema.safeParse(req.body);
  
  if (!result.success) {
    return res.status(400).json({ 
      error: "Invalid input data", 
      details: result.error.format() 
    });
  }

  // Process only validated, sanitized data
  const { email, password } = result.data;
  const user = await authenticateUser(email, password);
  return res.json(user);
});

Rule #3 — Rate Limiting Against Brute-Force & DoS Attacks

Without request throttling, your authentication or email endpoints can be flooded by automated bots.

const rateLimit = require('express-rate-limit');

// Strict limiter for authentication endpoints
const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5,                    // Max 5 attempts per IP
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: "Too many login attempts. Please try again in 15 minutes." }
});

// General API limiter
const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100
});

app.use('/api/auth/login', loginLimiter);
app.use('/api/', apiLimiter);

Rule #4 — CORS Configuration (Cross-Origin Resource Sharing)

❌ Dangerous CORS Configuration

// ❌ CRITICAL: Allows ANY external website to make authenticated requests with cookies!
app.use(cors({ origin: '*', credentials: true }));

✅ Secure CORS Configuration

const cors = require('cors');

const allowedOrigins = ['https://bmj-tech.com', 'https://admin.bmj-tech.com'];

app.use(cors({
  origin: function (origin, callback) {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Blocked by CORS policy'));
    }
  },
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true
}));

Rule #5 — Secure Session & JWT Token Storage

// ✅ Always store JWT tokens in HTTP-Only, SameSite=Strict cookies (NEVER in localStorage)
res.cookie('token', jwtToken, {
  httpOnly: true,  // Prevents XSS token theft via JavaScript
  secure: process.env.NODE_ENV === 'production', // Forces HTTPS
  sameSite: 'strict', // Prevents CSRF attacks
  maxAge: 3600000    // Expires after 1 hour
});

Security Checklist for Node.js / Express

  • [ ] Implement Helmet on all Express applications.
  • [ ] Validate 100% of incoming requests with Zod.
  • [ ] Configure Rate Limiting on sensitive routes (Login, Reset Password, Payments).
  • [ ] Restrict CORS to known trusted origin domains only.
  • [ ] Store access tokens in HTTP-Only, Secure, SameSite=Strict cookies.