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.
CORS — Cross-Origin Resource Sharing
Section titled “CORS — Cross-Origin Resource Sharing”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 — security headers
Section titled “Helmet — security headers”Helmet sets HTTP response headers that protect against common web vulnerabilities:
npm install helmetimport helmet from 'helmet'
app.use(helmet())That single line adds headers like:
| Header | Protects against |
|---|---|
X-Content-Type-Options: nosniff | MIME sniffing attacks |
X-Frame-Options: DENY | Clickjacking |
Strict-Transport-Security | Forces HTTPS |
Content-Security-Policy | XSS and data injection |
X-XSS-Protection | Cross-site scripting |
No configuration needed for the Bulletin API — the defaults are sensible.
express-rate-limit
Section titled “express-rate-limit”Rate limiting prevents a single client from making too many requests, protecting against brute-force attacks and API abuse:
npm install express-rate-limitimport rateLimit from 'express-rate-limit'
// General API rate limitconst 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)Full security middleware setup
Section titled “Full security middleware setup”// src/index.ts — complete middleware stackimport '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 middlewareapp.use(helmet())app.use(cors({ origin: config.frontendUrl }))app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }))
// Request processingapp.use(morgan('dev'))app.use(express.json({ limit: '10kb' })) // limit body size
// Routesimport authRouter from './routes/auth.js'import postsRouter from './routes/posts.js'app.use('/auth', authRouter)app.use('/posts', postsRouter)
// Error handlingapp.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.
Exercise
Section titled “Exercise”- Install
helmetandexpress-rate-limit. - Add both to your middleware stack.
- Add a stricter rate limit to the
/authrouter. - 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-limitprevents 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.