Security Best Practices
The previous pages in this section each cover a specific threat. This page is the consolidation: the practices that span every layer, the defaults you should configure on every project, and the ongoing habits that keep a deployed application secure as it evolves. Think of it as the checklist you run before shipping and the habits you build into your development workflow.
Configuration defaults for every Express app
import express from 'express'
import helmet from 'helmet'
import rateLimit from 'express-rate-limit'
import mongoSanitize from 'express-mongo-sanitize'
const app = express()
// Security headers:
app.use(helmet())
// Remove X-Powered-By (Helmet does this, but be explicit):
app.disable('x-powered-by')
// Parse bodies with a size limit — prevents request flooding:
app.use(express.json({ limit: '100kb' }))
app.use(express.urlencoded({ extended: true, limit: '100kb' }))
// Global rate limit — protect all endpoints:
app.use(rateLimit({ windowMs: 60_000, max: 200, standardHeaders: true }))
// Strip MongoDB operators from request body/params (if using Mongoose):
app.use(mongoSanitize())Input and output discipline
Rule | Why |
|---|---|
Validate all input with a schema | Reject bad shapes before they reach business logic |
Parameterize all queries | Prevents SQL/NoSQL injection categorically |
Escape output in templates | Prevents stored/reflected XSS |
Sanitize rich HTML with DOMPurify | Prevents stored XSS from user-generated content |
Explicit field selection in DB queries | Prevents over-fetching and accidental data exposure |
Never log passwords or tokens | Prevents credential leak via log aggregation |
Authentication and authorization
Hash passwords with bcrypt (≥10 rounds) or argon2id — never store plaintext.
Rate-limit and lock out login endpoints — block brute-force.
Short-lived tokens (15m access + refresh tokens) — limit stolen-token blast radius.
HttpOnly+Secure+SameSiteon all session/auth cookies.Deny by default — every route requires auth unless explicitly public.
Scope DB queries to the authenticated user — prevent IDOR.
Server-side authorization only — never trust client-side role checks.
Secrets and environment
No secrets in source control — environment variables or secrets manager only.
Validate required env vars at startup — fail loudly before accepting traffic.
Rotate secrets regularly and immediately on suspected exposure.
Separate secrets per environment — dev/staging/prod each have their own.
Use
npm ciin CI/CD — installs exactly from the lockfile.
Dependencies and supply chain
# In CI — fail on high/critical vulnerabilities: npm audit --audit-level high # Keep dependencies current: npm outdated # Review new packages before adding them
Defensive coding patterns
// 1. Explicit field allow-list — never spread req.body directly:
const { name, email } = req.body
await db.users.update(id, { name, email })
// 2. Always check ownership — never just findById:
const post = await db.posts.findOne({ id, authorId: req.user.id })
// 3. Avoid eval, Function(), child_process.exec with user input:
// ❌ exec(`convert ${req.body.filename}`)
// ✅ execFile('convert', [sanitizedFilename])
// 4. Set limits everywhere:
app.use(express.json({ limit: '100kb' })) // body size
app.get('/search', (req, res) => {
const limit = Math.min(Number(req.query.limit) || 20, 100) // page size cap
})Headers to verify on every deployment
curl -I https://yourapp.com | grep -E '(Content-Security|X-Frame|Strict-Transport|X-Content)' # Content-Security-Policy: default-src 'self'; ... # X-Frame-Options: DENY # Strict-Transport-Security: max-age=31536000; includeSubDomains # X-Content-Type-Options: nosniff
Ongoing security hygiene
Security-review new features during design — not after implementation.
Add auth tests for every protected route — unauthenticated, wrong role, correct role.
Run
npm auditin CI and treat high/critical as a build failure.Use Dependabot or Renovate for automated dependency update PRs.
Review logs for anomalies — many 401s/403s signal probing.
Grade your live site at securityheaders.com and SSL Labs periodically.
Have an incident response plan — who to notify, how to revoke, what to audit.