Skip to content

CORS, Helmet, and Rate Limiting

Authentication protects specific routes. These three tools protect the API as a whole — from browser security issues, missing security headers, and abuse.

You set up basic CORS in Module 04. Now let’s configure it properly for production:

import cors from 'cors'
const allowedOrigins = [
process.env.FRONTEND_URL, // production: GitHub Pages URL
'http://localhost:5173', // Vite dev server
'http://localhost:4173', // Vite preview
].filter(Boolean) as string[]
app.use(cors({
origin: (origin, callback) => {
// Allow requests with no origin (curl, Postman, server-to-server)
if (!origin) return callback(null, true)
if (allowedOrigins.includes(origin)) return callback(null, true)
callback(new Error(`CORS: origin ${origin} not allowed`))
},
credentials: true,
methods: ['GET', 'POST', 'PATCH', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
}))

credentials: true is needed if you ever use cookies (not needed for JWT in headers, but good to have).

Helmet sets HTTP response headers that protect against common web vulnerabilities:

Terminal window
npm install helmet
import helmet from 'helmet'
app.use(helmet())

That single line adds headers like:

HeaderProtects against
X-Content-Type-Options: nosniffMIME sniffing attacks
X-Frame-Options: DENYClickjacking
Strict-Transport-SecurityForces HTTPS
Content-Security-PolicyXSS and data injection
X-XSS-ProtectionCross-site scripting

No configuration needed for the Bulletin API — the defaults are sensible.

Rate limiting prevents a single client from making too many requests, protecting against brute-force attacks and API abuse:

Terminal window
npm install express-rate-limit
import rateLimit from 'express-rate-limit'
// General API rate limit
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per 15 minutes
message: { error: 'Too many requests, please try again later' },
standardHeaders: true, // Return rate limit info in RateLimit-* headers
legacyHeaders: false,
})
// Stricter limit for auth endpoints (prevent brute force)
const authLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 10, // 10 login attempts per hour
message: { error: 'Too many auth attempts, please try again later' },
})
app.use('/api', apiLimiter)
app.use('/auth', authLimiter)
// src/index.ts — complete middleware stack
import 'dotenv/config'
import express from 'express'
import cors from 'cors'
import helmet from 'helmet'
import morgan from 'morgan'
import rateLimit from 'express-rate-limit'
import { config } from './config.js'
import { errorHandler } from './middleware/errorHandler.js'
const app = express()
// Security middleware
app.use(helmet())
app.use(cors({ origin: config.frontendUrl }))
app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }))
// Request processing
app.use(morgan('dev'))
app.use(express.json({ limit: '10kb' })) // limit body size
// Routes
import authRouter from './routes/auth.js'
import postsRouter from './routes/posts.js'
app.use('/auth', authRouter)
app.use('/posts', postsRouter)
// Error handling
app.use((req, res) => res.status(404).json({ error: 'Not found' }))
app.use(errorHandler)
app.listen(config.port)

express.json({ limit: '10kb' }) prevents large payload attacks — no legitimate Bulletin post needs more than 10KB.

  1. Install helmet and express-rate-limit.
  2. Add both to your middleware stack.
  3. Add a stricter rate limit to the /auth router.
  4. Check response headers in the browser DevTools Network tab — confirm Helmet’s headers are present.
  • cors({ origin }) allows requests from your frontend URL — configure all allowed origins.
  • helmet() sets security headers that protect against common web vulnerabilities — one line.
  • express-rate-limit prevents brute-force attacks — stricter limits for auth endpoints.
  • express.json({ limit: '10kb' }) prevents large payload attacks.
  • These should be among the first middleware registered, before routes.