Skip to content

Latest commit

 

History

History
330 lines (214 loc) · 10.4 KB

File metadata and controls

330 lines (214 loc) · 10.4 KB

IMPROVEMENTS.md

Performance & Security Improvements (Node.js + Express.js + MongoDB – JavaScript)

This document explains all improvements (10 points) that were applied to the Backend-Template-JavaScript project to increase security and performance according to common best practices used in professional projects.


Quick Summary (Executive Summary)

  • The server startup sequence was improved to ensure the server does not start before a successful MongoDB connection.
  • The security layer was strengthened using essential middlewares (Security Headers / Rate Limiting / Sanitization).
  • Cookies and tokens handling was hardened (Cookie-only auth) and Refresh Tokens security was improved with hashing + rotation + reuse detection.
  • File uploads (Multer) were hardened to prevent unsafe/large uploads and to prevent direct access to temporary files.
  • Error handling was unified by adding a 404 handler and a global error handler.
  • Sensitive logging was removed and replaced with a professional logger with redaction to prevent leaking sensitive data.

1) Move listen to index.js after successful DB connection (Startup Sequencing)

Problem

The server could start (app.listen) before ensuring a successful database connection, which can cause runtime errors when requests depend on MongoDB.

Solution

  • app.js was made responsible only for configuring middlewares/routes and exporting app.
  • The server start (listen) was moved to index.js after connectDB() succeeds.

Affected Files

  • src/app.js
  • src/index.js

How to Test

  • Disconnect MongoDB or set a wrong URI → the server should not start normally.
  • When DB succeeds → the server starts and prints the startup message.

2) Helmet + Secure Headers (Hardening)

Problem

The project lacked essential HTTP security headers (e.g., preventing sniffing, clickjacking protection, referrer policy, etc.).

Solution

helmet was added to enable secure headers with API-friendly settings, and x-powered-by was disabled to reduce information disclosure.

Affected Files

  • src/app.js
  • package.json

How to Test

  • Call any endpoint and check the response headers (e.g., X-Content-Type-Options and others).

3) Rate Limiting for (login/register/refresh)

Problem

Auth endpoints are commonly targeted by:

  • Brute force attacks
  • Credential stuffing
  • DoS on sensitive routes

Solution

express-rate-limit was added and a dedicated limiter was applied to:

  • /register
  • /login
  • /refresh-token

(The limiter was placed before upload on register to reduce resource usage under abuse.)

Affected Files

  • src/middlewares/rateLimit.middleware.js
  • src/routes/user.routes.js
  • package.json

How to Test

  • Repeat login requests rapidly above the configured limit → you should receive 429 Too Many Requests.

4) Sanitization: mongo-sanitize + XSS sanitize + HPP

Problem

  • MongoDB apps may be vulnerable to NoSQL injection via operators like $gt and $ne.
  • Inputs may contain XSS payloads.
  • HTTP Parameter Pollution can cause unexpected behavior when query params are repeated.

Solution

Three global protection layers were added before routes:

  • express-mongo-sanitize to mitigate NoSQL injection
  • express-xss-sanitizer to sanitize inputs against XSS
  • hpp to protect against HTTP Parameter Pollution

Affected Files

  • src/app.js
  • package.json

How to Test

  • Send a body containing { "email": { "$gt": "" } } to login → it should not behave as an injected query.
  • Try repeated query params intentionally → their impact should be reduced.

5) CORS Whitelist + Fix credentials

Problem

  • There was a CORS option mistake: using credential instead of credentials.
  • Using * with credentials: true is practically incorrect and insecure.
  • A whitelist was needed to control which browser origins are allowed.

Solution

  • A whitelist was built from CORS_ORIGIN as a comma-separated list of allowed origins.
  • credentials: true was correctly configured.
  • Requests without an Origin (Postman/curl) were allowed to support development.
  • Preflight handling was fixed without relying on "*" which can cause issues in some environments.

Affected Files

  • src/app.js
  • .env.example (and/or environment configuration)

How to Test

  • From a non-whitelisted origin → the browser should block the request (CORS error).
  • From an allowed origin → the request should succeed.
  • Postman/curl → usually works because there is no Origin header.

6) Cookie Hardening + Do not return tokens in JSON (Cookie-only Auth)

Problem

  • Returning accessToken/refreshToken inside JSON responses increases the chance of leakage through XSS, logs, or debugging tools.
  • Cookie settings were incomplete (sameSite/maxAge) or configured in a way that could break development.

Solution

  • Authentication was changed to rely on HttpOnly cookies only.
  • Cookie options were hardened:
    • httpOnly: true
    • secure depending on environment (Production vs Development)
    • sameSite depending on the scenario
    • maxAge derived from token expiry
  • Tokens were no longer returned in JSON.
  • verifyJWT was updated to read the token from cookies (with optional support for Authorization header).

Affected Files

  • src/controllers/user.controller.js
  • src/middlewares/auth.middleware.js
  • src/utils/cookies.js (or a similar helper file)
  • package.json

How to Test

  • Login → should return user/success message without tokens in JSON, and cookies should be set.
  • Any protected route → should work using cookies.

7) Refresh Token: Hashing + Rotation + Revoke on Reuse

Problem

  • Storing refresh tokens in plaintext in the DB is a major risk (if DB leaks, sessions are compromised).
  • Without rotation, the same refresh token can be reused for a long time.
  • Without reuse detection, a stolen token can continue to work.

Solution

  • Refresh tokens are stored as a hash in the DB rather than the raw token.
  • On every refresh:
    • A new refresh token is issued (Rotation).
    • The stored hash is updated.
  • Revoke on reuse:
    • If an incoming refresh token does not match the stored hash, it is treated as a reuse attempt and the session is revoked (hash removed) and the user must login again.

Affected Files

  • src/models/user.model.js (replacing refreshToken with refreshTokenHash)
  • src/controllers/user.controller.js
  • src/middlewares/auth.middleware.js

How to Test

  1. Login → receive refresh cookie.
  2. Refresh once → should issue a new refresh cookie (rotation).
  3. Try using the old refresh token → should return 401 and revoke the session.

8) Multer Hardening (limits/fileFilter/random name/outside public)

Problem

  • Using file.originalname can lead to:
    • overwrite issues
    • unsafe file names
  • Uploading inside public can expose uploaded files directly via static hosting.
  • Missing limits/fileFilter allows large or dangerous uploads.

Solution

  • Files are stored outside public (e.g., tmp/uploads).
  • Filenames are generated randomly (UUID/crypto) with a controlled extension.
  • limits were added for file size and count.
  • fileFilter was added to allow specific types only (images: jpg/png/webp).
  • Upload directory is created automatically if it does not exist.

Affected Files

  • src/middlewares/multer.middleware.js

How to Test

  • Upload a valid image → succeeds.
  • Upload a pdf/exe → rejected.
  • Upload a file larger than the limit → rejected (returned as 400 with the error handler).

9) Global Error Handler + 404 Handler

Problem

  • Without a 404 handler, responses are not consistent.
  • Without a centralized error handler, it can result in:
    • stack trace leakage
    • inconsistent responses
    • excessive repetitive try/catch blocks

Solution

  • Added a notFound middleware for routes that do not exist.
  • Added a centralized errorHandler that:
    • returns a consistent JSON response
    • hides stack traces in production
    • handles common errors (Multer/Mongoose) with appropriate responses

Affected Files

  • src/middlewares/error.middleware.js
  • src/app.js

How to Test

  • Call a non-existing route → 404 with a clear message.
  • Trigger an error intentionally (e.g., upload a large file) → 400/500 in a structured response.

10) Remove sensitive logging + Logger with Redaction

Problem

  • Logging req.body or req.files using console.log can leak:
    • passwords
    • tokens
    • cookies
    • other sensitive data
  • console.log is not production-grade and does not support log levels or structured output.

Solution

  • Removed any logs that printed sensitive contents.
  • Added a professional logger (e.g., Pino) with:
    • log levels (info/warn/error)
    • request logging
    • redaction to remove/hide sensitive paths (authorization/cookies/password/token)
  • Integrated logger with the error handler to record errors safely.

Affected Files

  • src/utils/logger.js
  • src/middlewares/logger.middleware.js
  • src/controllers/* (removing sensitive console.log)
  • src/db/* and src/index.js (replacing console.log with logger)
  • src/middlewares/error.middleware.js
  • package.json

How to Test

  • Perform login with a password and verify that logs do not show password/token/cookie values.

What I Learned (New Topics I Learned)

  • Server startup sequencing: DB before listen to prevent runtime failures.
  • Helmet and secure HTTP headers to reduce attack surface.
  • Rate limiting to protect authentication endpoints from brute force and DoS.
  • The difference between NoSQL injection, XSS, and HPP and how each is mitigated.
  • Correct CORS configuration with credentials and why * cannot be used with it.
  • Cookie security flags: httpOnly, secure, sameSite, maxAge, and how they differ by environment.
  • Refresh token security: hashing, rotation, reuse detection, and revocation.
  • Upload security: fileFilter/limits and avoiding public upload storage.
  • Centralized Express error handling without leaking sensitive information.
  • Production-grade logging with redaction to prevent secrets leakage and improve observability.

Notes

  • .env must not be pushed to GitHub; only .env.example should be committed.
  • If the frontend is hosted on a different domain in production, cookies often require sameSite: "none" with secure: true, and CORS whitelist must be configured correctly.