MERN Stack Security Blueprint

Share

To secure a MERN Stack application, you need to: (1) use helmet for security headers and express-mongo-sanitize to prevent NoSQL injection, (2) implement proper JWT authentication middleware, (3) use mongoose schema validation for input sanitization, (4) configure CORS properly with specific origins, and (5) always get user IDs from auth middleware (never from request body). This blueprint covers Express security middleware with MongoDB protection.

TL;DR

MERN Stack requires careful attention to NoSQL injection, Express middleware security, and JWT handling. Use mongoose with schema validation, sanitize all inputs, implement proper CORS, and store JWTs securely. Never trust client-provided user IDs.

Express Security Middleware Express

server/app.js
import express from 'express'
import helmet from 'helmet'
import cors from 'cors'
import rateLimit from 'express-rate-limit'
import mongoSanitize from 'express-mongo-sanitize'

const app = express()

// Security headers
app.use(helmet())

// CORS configuration
app.use(cors({
  origin: process.env.CLIENT_URL,
  credentials: true,
}))

// Rate limiting
app.use(rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
}))

// Prevent NoSQL injection
app.use(mongoSanitize())

app.use(express.json({ limit: '10kb' }))

JWT Authentication Middleware Node.js

server/middleware/auth.js
import jwt from 'jsonwebtoken'

export const protect = async (req, res, next) => {
  const token = req.cookies.token || req.headers.authorization?.split(' ')[1]

  if (!token) {
    return res.status(401).json({ error: 'Not authorized' })
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET)
    req.user = await User.findById(decoded.id).select('-password')

    if (!req.user) {
      return res.status(401).json({ error: 'User not found' })
    }

    next()
  } catch (error) {
    return res.status(401).json({ error: 'Invalid token' })
  }
}

Mongoose Schema Validation MongoDB

server/models/Post.js
import mongoose from 'mongoose'

const postSchema = new mongoose.Schema({
  title: {
    type: String,
    required: [true, 'Title is required'],
    maxlength: [200, 'Title cannot exceed 200 characters'],
    trim: true,
  },
  content: {
    type: String,
    required: true,
  },
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: true,
  },
}, { timestamps: true })

export default mongoose.model('Post', postSchema)

Protected Route Example Express

server/routes/posts.js
import express from 'express'
import { protect } from '../middleware/auth.js'
import Post from '../models/Post.js'

const router = express.Router()

router.post('/', protect, async (req, res) => {
  const { title, content } = req.body

  const post = await Post.create({
    title,
    content,
    author: req.user._id,  // Use verified user from middleware
  })

  res.status(201).json(post)
})

router.get('/mine', protect, async (req, res) => {
  const posts = await Post.find({ author: req.user._id })
  res.json(posts)
})

Never trust client IDs. Always use req.user from your auth middleware, never req.body.userId or similar client-provided values.

Security Checklist

Pre-Launch Checklist

Helmet middleware enabled

CORS properly configured

Rate limiting implemented

express-mongo-sanitize used

JWT secret is strong and in env

User IDs from auth middleware only

Alternative Stacks

Consider these related blueprints:

Check Your MERN Stack App

Scan for injection and auth issues.

Start Free Scan
Security Blueprints

MERN Stack Security Blueprint