By framework
Express (Node.js) security — a production hardening reference
An Express (Node.js) production-hardening reference: being minimal, you add the defenses — helmet headers, npm CVEs, input validation, authorization, rate limiting, sessions/CSRF, and SSRF. Defensive, no attack steps.
For: anyone running an API or app on Express (Node.js). No attack steps here — this is a working reference for the defenses you add to a minimal framework: a priority-ordered checklist, per-area guidance, and self-verification. For the cross-framework picture, see the security-by-framework hub.
Priority-ordered hardening checklist
Do this table top-down. P0 is a prerequisite, P1 is the most frequent source of incidents, P2 is ongoing operational hygiene.
P0 ── Prerequisite (do first)
Headers (helmet) + disable x-powered-by / npm dependency CVE monitoring / secrets from env, no stack exposure
P1 ── Top source of incidents
Input validation and injection defense / owner-scoped authorization
P2 ── Operational hygiene
Rate limiting and size caps / sessions, cookies, CSRF / SSRF
| Priority | Control | Specifics (Express) |
|---|---|---|
| P0 | Security headers | helmet-style: CSP/HSTS/X-Content-Type, etc. Disable x-powered-by |
| P0 | npm dependency CVEs | Monitor with npm audit/osv-scanner; judge by running version, patch fast |
| P0 | Secrets and errors | Secrets from env vars. Don't expose stack traces in production |
| P1 | Input validation | Validate/sanitize inputs (type/range/allowed). Body-size caps |
| P1 | Injection | Bind DB queries. Watch NoSQL operator injection ($). Don't pass input to eval |
| P1 | Authorization | Authentication + owner-scoped authorization on each route. JWT: verify signature, pin alg |
| P2 | Rate limiting | Attempt/throughput limits on login/APIs. Curb brute force, abuse, DoS |
| P2 | Sessions/cookies | secure/httpOnly/sameSite. CSRF (cookie sessions). Production session store |
| P2 | SSRF | Allowlist server-side fetches + block internal IPs/metadata |
1. Security headers and reducing exposure (P0)
Express doesn't set security headers by default. Lift this baseline first.
- Use helmet-style middleware to add CSP, HSTS, X-Content-Type-Options, frameguard, etc.
- Disable
x-powered-byto reduce framework/version exposure. - Check your own site with the security headers checker.
2. Dependencies (npm) and supply chain (P0)
- Machine-monitor dependency CVEs with
npm auditor osv-scanner, judge by the running version, and patch fast (→ monitoring dependency CVEs). - Node has a large dependency tree and supply-chain risk (typosquatting,
postinstallhooks). Vet at install time and keep the count down. - Keep Node on a supported (LTS) version and don't leave EOL versions in place.
3. Input validation and injection (P1)
- Validate/sanitize all input (body, query, params, headers). Set a body-size cap to curb DoS.
- SQL: bind with placeholders; never build queries by string concatenation (→ what SQL injection is).
- NoSQL: watch for operator injection (
$) via object-typed input (don't pass raw objects into queries). - Don't pass external input to dangerous evaluation like
eval.
4. Authentication and authorization (P1)
Common (dangerous)
- no authorization — "logged in = allowed"
- relying on middleware order/presence alone
- trusting client-supplied IDs/flags as-is
- JWT signature unverified /
alg:noneallowed
Correct
- authentication plus owner-scoped authorization on each route
- verify close to the data (don't rely on order)
- re-verify on the server (resist ID swaps)
- JWT signature verification + pinned alg (reject
alg:none)
See what IDOR is · what JWT is. You can inspect a JWT's contents yourself (JWT decoder).
5. Rate limiting and abuse (P2)
- Apply attempt/throughput limits to login, APIs, and password reset to curb brute force, abuse, and DoS.
- Set size caps on request bodies and uploads (prevent exhaustion from large payloads).
6. Sessions and cookies (P2)
- Set
secure/httpOnly/sameSiteon session cookies. - For cookie-session apps, add CSRF protection (→ what CSRF is).
- Use a proper session store in production (the default in-memory store isn't for production). Regenerate the session on login.
7. SSRF and server-side fetches (P2)
- Server-side fetching of user-supplied URLs should restrict targets to an allowlist and block reaching internal IPs / cloud metadata at connection time (→ what SSRF is).
8. Error handling and production config (P0–P2)
- Use a central error handler and don't expose stack traces externally.
- Run with
NODE_ENV=production. Behind a proxy, settrust proxycorrectly so scheme and client IP aren't misjudged. - Terminate TLS/HTTPS reliably.
Verify: is your Express actually hardened?
Building it isn't the end — it's done only once you've checked. These are defensive self-checks against your own environment.
Headers and exposure
x-powered-by is gone, via the headers checker.Errors don't leak internals
Authorization holds
Dependencies, rate limiting, SSRF
npm audit/osv is clean, login rate-limiting works, and outbound fetches can't reach internal IPs.This site's view: a minimal framework pairs freedom with responsibility
Express's appeal is its lightness and freedom — which means you design the defense too. What Rails and Laravel guard by default (headers, CSRF, an authorization scaffold), on Express you wire in deliberately. The center of gravity is working the table above top-down — set headers, validate input, write authorization on public entry points, and monitor dependencies for CVEs. Node especially has a large dependency count, so dependency freshness decides incidents. Drop the assumption that "the framework will guard it" and add the defenses explicitly.
Read next
- Hub: security by framework · Next.js security (also Node)
- Practice: monitoring dependency CVEs · the vulnerability-response playbook
- Glossary: what IDOR is · what SSRF is · what JWT is · what CSRF is
- Tools: security headers checker · JWT decoder
FAQ
QWhat should I do first to secure Express?
The three P0 items: (1) add security headers (CSP/HSTS, etc.) with helmet-style middleware and disable x-powered-by; (2) machine-monitor npm dependency CVEs and patch fast, judging by the running version; (3) read secrets from the environment and don't expose stack traces in production. Express won't guard these for you by default, so adding them is the baseline. Next, move to input validation, authorization, and rate limiting.
QDo I need security headers like helmet?
Yes. Express doesn't set security-related HTTP headers by default. Use helmet-style middleware to add CSP, HSTS, X-Content-Type-Options, etc., lowering basic risks like clickjacking and MIME sniffing. Also disable x-powered-by to reduce framework exposure. It's a baseline lift just by adding it, so put it in first.
QHow do I set up authentication and authorization?
After an authentication middleware confirms login, always write owner-scoped authorization on each route — that the target truly belongs to the user. Don't rely on middleware order or mere presence; verify close to the data. If you use JWT, require signature verification and pin the alg (reject alg:none).
QHow do I manage npm dependency vulnerabilities?
Machine-monitor known CVEs with npm audit or osv-scanner and patch fast, judging by the running version. Node has a very large dependency tree and supply-chain risk (typosquatting, postinstall hooks), so dependency freshness and vetting at install time make the difference. Keep Node on a supported (LTS) version too.
QWhat's the bare minimum?
(1) helmet-style headers + disable x-powered-by, (2) validate/sanitize all input, (3) owner-scoped authorization, (4) rate limits + body-size caps on login/APIs, (5) npm dependency CVE monitoring + fast patching, (6) don't expose stack traces in production. This minimum set fills most of the gaps of a minimal framework.