Join our Newsletter — 33% off our NHI Course
Home› Guides› Express.js Security Guide: Helmet, Sessions, Rate Limiting and…
Guide Application Security

Express.js Security Guide: Helmet, Sessions, Rate Limiting and Safe Middleware

PDF for newsletter subscribers · free to read online

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 27 October 2026 15 min read
On this page

Express gives you a router and a middleware chain, and almost nothing else, so every security property of an Express application is something a team chose to add, configure and keep in order. The defaults are deliberately minimal: no security headers, a session cookie that is not marked Secure, no rate limiting, a fingerprinting header and an in-memory session store that the maintainers say is not for production. This guide turns the official Express production security guidance, the Helmet and express-session documentation and the OWASP Node.js cheat sheet into a hardening baseline for Express 4 and 5. It is written for Node.js developers, application security engineers and reviewers who own or assess Express services, and it goes deeper on Express specifics than the general Node.js Security Guide.

Key takeaways

  • Middleware order is the security model. Helmet, body limits, rate limiting, session and authentication must run before the routes they protect, and the error handler must be last.
  • express-session defaults need changing. The cookie is HttpOnly but not Secure, SameSite is off, the name is the fingerprintable connect.sid and MemoryStore is not designed for production.
  • trust proxy decides who your users are. req.ip, req.protocol and req.hostname come from X-Forwarded-* headers once it is enabled, so a wrong value breaks rate limiting, Secure cookies and audit logs.
  • Helmet is a baseline, not a policy. It sets 13 headers with sensible defaults, but the Content Security Policy still has to be written for your application and tested in report-only mode first.
  • Stay on a patched major version. Express 3.x has had no fixes since 2015, and recent 4.x releases fixed open redirect and XSS flaws in res.redirect and res.location, so version currency is a control in itself.

How identity and access work in Express

Express has no built-in user model. Identity arrives through middleware you add: a session layer, an authentication strategy library or a token verifier, followed by your own authorisation checks. That makes the request pipeline the place where identity can be lost, spoofed or skipped.

Sessions: express-session versus cookie-session

The Express best practices page describes two session modules. express-session stores session data on the server and puts only a signed session ID in the cookie. cookie-session serialises the whole session into the cookie, which the Express team says suits only small sessions of primitive values, kept under 4093 bytes per domain, and only where the client may see the data.

Option (express-session)DefaultProduction setting and why
secretRequired, no defaultAt least 32 bytes of entropy from a secrets store, not the repository. Pass an array to rotate: the first element signs, all are checked.
nameconnect.sidA generic name such as sid, ideally with the __Host- prefix (ASVS 5.0 V3.3.3) when the cookie is host-only and Secure.
cookie.securefalsetrue on HTTPS sites. Behind a proxy this also needs trust proxy, or the cookie is never sent.
cookie.httpOnlytrueKeep. Script access to session IDs is what XSS-driven session theft relies on.
cookie.sameSiteNot set'lax' as a floor, 'strict' where cross-site navigation into authenticated pages is not needed.
cookie.maxAgenull (browser session)An absolute lifetime that matches your session policy; combine with rolling: true for idle timeouts.
resave / saveUninitializedtrue (both deprecated defaults)false unless the store requires otherwise. saveUninitialized: false avoids setting cookies before login or consent.
storeMemoryStoreA production store (Redis, a database). MemoryStore leaks memory and does not scale past one process.

Call req.session.regenerate() after successful login so the pre-authentication session ID is discarded, which is the documented defence against fixation (see the Session Fixation Guide). At logout, clear the user, save and regenerate, or call req.session.destroy(), and make sure the server-side record is removed, not just the cookie.

Authentication and authorisation middleware

  • Mount order: authentication middleware must be mounted before the routers it protects. A router mounted above app.use(requireAuth) is public, and this is the most common Express access control bug found in code review.
  • Deny by default: apply an authentication guard at the router level and explicitly mark the few public routes, rather than adding a guard to each private route and hoping none is missed.
  • Object-level checks: a valid session says who the caller is, not what they may touch. Check ownership or tenancy for every :id parameter in the handler or a shared policy function.
  • Token APIs: for bearer tokens, verify signature, issuer, audience and expiry in middleware and pin the accepted algorithms. See the JWT Security Guide.
  • Express 5 async handling: rejected promises from async handlers now go to the error handler. In Express 4 an unawaited rejection inside an auth check can leave a request hanging or skip the check, so wrap async middleware or upgrade.

Admin roles and privileged access

Express apps usually grow an admin surface: an /admin router, a health or metrics endpoint, a debug route someone added during an incident. These are privileged functions and deserve stronger controls than the rest of the app.

  • Separate the router: mount admin routes on their own router with a role check applied once at the top, and consider serving them on a separate hostname or internal-only listener so the same-origin policy and network controls also apply (ASVS 5.0 V3.5.4).
  • Step-up authentication: require a recent strong authentication for destructive admin actions, and record the session's authentication time to enforce it.
  • Use state-changing methods correctly: sensitive actions on POST, PUT, PATCH or DELETE only (ASVS 5.0 V3.5.3), with CSRF protection for cookie-authenticated forms. The OWASP Node.js cheat sheet notes that csurf is deprecated; see the CSRF Prevention Guide for current patterns.
  • No debug in production: remove or gate debug and documentation routes (ASVS 5.0 V13.4.2, V13.4.5), and set NODE_ENV=production so Express and template engines stop returning verbose errors.
  • Static files: Express 5 ignores dotfiles and hidden directories in express.static() and res.sendFile() by default. On Express 4, set dotfiles: 'ignore' or 'deny' and never serve the project root, which exposes .git and .env (ASVS 5.0 V13.4.1).

Integrations, API tokens and service accounts

Most Express services hold machine credentials: database passwords, cloud keys, payment and email API keys, the session signing secret and outbound OAuth client secrets. These non-human identities are often more powerful than any user session.

  • Secrets out of code: load the session secret and API keys from a secrets manager or injected environment at start-up, never from a committed .env. Rotate with the array form of secret so users stay logged in. See the Secrets Management Guide.
  • Least privilege per integration: give each downstream key the narrowest scope the service needs, and separate read and write credentials where the provider allows. See the API Key Management Guide.
  • Inbound service tokens: for service-to-service calls into the app, prefer mTLS or short-lived signed tokens over static shared keys, and verify them in dedicated middleware (ASVS 5.0 V13.2.1).
  • Outbound allowlists: if a route fetches a URL supplied by a user or a webhook, restrict destinations to an allowlist (ASVS 5.0 V13.2.4, V13.2.5) to prevent the app's network identity being used for SSRF.
  • Dependencies are integrations too: an Express app typically pulls hundreds of transitive packages that run with the same access to those secrets. Use npm ci from a lockfile, run npm audit in CI and read the Malicious Package and Dependency Confusion Guide.

Logging and detection

Express logs nothing by default. Add structured request logging (for example a JSON logger middleware) and application events, and make sure the client IP in those logs is the real one.

Get trust proxy right first

The Express documentation sets trust proxy to false by default, so req.ip is the socket address. Behind a load balancer every request then appears to come from the proxy, and a rate limiter keyed on IP will throttle everyone at once. Setting it to true trusts the left-most X-Forwarded-For entry, which a client can forge unless the last proxy overwrites the header. The safer choices are a hop count that matches a single fixed path (for example 1) or the proxy's addresses or subnets ('loopback', 'uniquelocal' or explicit CIDRs). ASVS 5.0 V15.3.4 requires that proxies pass the original IP in fields that cannot be manipulated.

What to log and alert on

EventWhere to capture itDetection use
Login success and failure, with username and client IPAuthentication handlerCredential stuffing (ATT&CK T1110.004) and brute force (T1110)
Rate limit rejections (HTTP 429)Limiter handler callbackAbuse sources, and misconfigured trust proxy if one IP dominates
Session regenerate, destroy and privilege changeSession and admin codeSession replay after logout, use of stolen cookies (T1539)
Authorisation denials (HTTP 403) by route and userPolicy middlewareObject enumeration and horizontal privilege attempts
Unhandled errors and HTTP 500 rateFinal error handlerExploit attempts against parsers and handlers (T1190)
CSP violation reportsReport endpoint configured in the policy (ASVS 5.0 V3.4.7)Injected scripts and policy gaps

Never log session IDs, bearer tokens, the Authorization header or request bodies from login and payment routes. See the JWT and Session Tokens in Code and Logs Guide and the Secure Error Handling and Logging Guide.

Metrics that show the controls work

  • Header coverage: percentage of production routes returning CSP, HSTS, X-Content-Type-Options and COOP, checked by an automated scan.
  • Cookie compliance: share of session and auth cookies with Secure, HttpOnly, SameSite and a __Host- prefix.
  • Patch latency: days from an Express, Node.js or middleware advisory to deployment, against your documented remediation window (ASVS 5.0 V15.1.1).
  • Login abuse blocked: rate limit and lockout events per day on authentication routes, trended against successful logins.
  • Unprotected routes: count of routes reachable without authentication that are not on the documented public list, from a route inventory test.

Hardening baseline

Recommended middleware order

  1. app.disable('x-powered-by') and app.set('trust proxy', ...) matched to your real proxy path.
  2. helmet() with an application-specific Content Security Policy.
  3. A general rate limiter, plus a stricter one on login, password reset and token endpoints.
  4. Body parsers with explicit limits, for example express.json({ limit: '100kb' }), sized to the largest legitimate payload. The OWASP cheat sheet uses limits as low as 1kb for small forms.
  5. Session middleware, then authentication, then authorisation routers.
  6. Routes.
  7. A custom 404 handler and a final error handler that logs the stack server-side and returns a generic message.

What Helmet sets by default

HeaderHelmet defaultNotes for Express teams
Content-Security-Policydefault-src 'self', object-src 'none', script-src 'self', script-src-attr 'none', frame-ancestors 'self', upgrade-insecure-requests and othersTune directives; start with reportOnly: true. The default style-src includes 'unsafe-inline'.
Strict-Transport-Securitymax-age=31536000; includeSubDomainspreload is off by default; add it only after confirming every subdomain is HTTPS.
Cross-Origin-Opener-Policysame-originUse same-origin-allow-popups if OAuth or payment pop-ups break.
Cross-Origin-Resource-Policysame-originRelax to same-site for assets served to sibling subdomains.
Referrer-Policyno-referrerStrict; some analytics expect strict-origin-when-cross-origin.
X-Content-Type-OptionsnosniffRequired by ASVS 5.0 V3.4.4.
X-Frame-OptionsSAMEORIGINLegacy; CSP frame-ancestors is the modern control.
Origin-Agent-Cluster, X-DNS-Prefetch-Control, X-Download-Options, X-Permitted-Cross-Domain-Policies?1, off, noopen, noneLow cost; leave on.
X-Powered-By / X-XSS-ProtectionRemoved / 0Helmet disables the legacy XSS filter because it made things worse.

Cross-Origin-Embedder-Policy is not set by default; enable it only if you need cross-origin isolation. The Security Headers Guide covers each header in depth.

Rate limiting choices

The Express team recommends blocking on two signals for login: consecutive failures for the same username and IP, and total failures from an IP over a longer window (their example is 100 failures in a day), and points to rate-limiter-flexible. express-rate-limit is a simpler per-route limiter with windowMs, limit and IETF RateLimit headers through standardHeaders. Choose by deployment:

  • Single instance, low risk: express-rate-limit with the default memory store is acceptable.
  • Several instances or containers: use a shared store (Redis or similar), or each replica counts separately and the effective limit multiplies.
  • Login and recovery endpoints: key on username plus IP as well as IP alone, so distributed credential stuffing against one account is still slowed. Keep IPv6 grouping (express-rate-limit's ipv6Subnet) so one host cannot rotate through a /64.
  • Large estates: enforce coarse limits at the edge or API gateway and keep account-aware limits in the app. See the Rate Limiting and API Abuse Guide.

Input, parsing and redirects

  • Type checks: query strings can parse into arrays or objects. Express 5 defaults to the simple query parser and express.urlencoded to extended: false; keep those unless you need nested input, and validate types (ASVS 5.0 V15.3.5). Use hpp or explicit handling against parameter pollution (V15.3.7).
  • Prototype pollution: never merge request bodies into objects with recursive merge helpers; use schema validation and Map where keys are user controlled (V15.3.6). See the Prototype Pollution Guide.
  • Templates: keep auto-escaping on and never compile user input as a template. See the Server-Side Template Injection (SSTI) Guide.
  • Redirects: validate the target host with new URL() against an allowlist before res.redirect(), and do not trust Referer as a redirect target now that Express 5 has removed the 'back' shortcut. See the Open Redirect Guide.
  • Dangerous functions: no eval(), no child_process.exec with request data, and regular expressions checked for catastrophic backtracking (ReDoS), with eslint-plugin-security in CI.

Phased rollout

  1. Inventory: list Express and Node.js versions, middleware, routes and which are public. Upgrade any 3.x service immediately and bring 4.x to at least 4.21.2, or plan the move to 5.
  2. Low-risk wins: disable X-Powered-By, add custom error handlers, set body limits and fix trust proxy. Verify req.ip in logs from a known test client.
  3. Headers in report mode: deploy Helmet with contentSecurityPolicy: { reportOnly: true }, collect violations for one or two release cycles, then enforce.
  4. Sessions: move to a production store, rename the cookie, set Secure and SameSite, and add regenerate on login. Expect a one-time logout when the cookie name changes; schedule it.
  5. Rate limits: start with logging-only thresholds, compare against real traffic, then enforce on authentication routes first.
  6. Rollback: keep each change behind configuration so a header or limiter can be relaxed without a code release, and keep the previous session secret in the array during rotation so a rollback does not invalidate sessions.

Framework mapping

Express controlOWASP ASVS 5.0NIST SP 800-53 Rev 5ATT&CK threat addressed
Session cookie attributes, regenerate on loginV3.3.1 to V3.3.4, V7 Session ManagementSC-23 Session AuthenticityT1539 Steal Web Session Cookie
Login rate limiting and lockoutV6 AuthenticationAC-7 Unsuccessful Logon AttemptsT1110 Brute Force, T1110.004 Credential Stuffing
Helmet headers and CSPV3.4.1 to V3.4.8SC-8 Transmission Confidentiality and Integrity (HSTS)T1190 Exploit Public-Facing Application
Input validation, body limits, HPPV15.3.5, V15.3.7, V2SI-10 Information Input ValidationT1190
Version currency and dependency auditV15.1.1, V15.2.1SI-2 Flaw Remediation, RA-5T1195.001 Compromise Software Dependencies and Development Tools
No fingerprinting or debug outputV13.4.2, V13.4.6CM-7 Least FunctionalityReconnaissance support

Common misconfigurations

  • trust proxy set to true on a directly reachable app: clients forge X-Forwarded-For, bypass IP rate limits and poison audit logs. Match the setting to the real hop count or proxy subnet.
  • Secure cookies that silently stop working: cookie.secure: true behind a TLS-terminating proxy without trust proxy means express-session never sets the cookie, and teams "fix" it by turning Secure off.
  • MemoryStore in production: sessions vanish on restart, memory grows without bound and multiple replicas disagree about who is logged in.
  • Routers mounted before authentication: a new router added near the top of app.js is public by accident. Test unauthenticated access to every route in CI.
  • Helmet added, CSP disabled: teams turn off contentSecurityPolicy to stop breakage and never return to it, losing the main XSS mitigation. Use report-only instead. See the Cross-Site Scripting (XSS) Prevention Guide.
  • Stack traces to users: the default error handler or a res.send(err) leaks paths and library versions. Use a final handler that returns a generic message.
  • Permissive CORS on cookie-authenticated APIs: reflecting any Origin with credentials allowed lets other sites read authenticated responses. See the CORS Misconfiguration Guide.

Practitioner checklist

  • Express is on a maintained major with all published security releases applied (4.21.2 or later, or 5.x), and Node.js is on a supported line.
  • x-powered-by is disabled and custom 404 and error handlers return no stack traces in production.
  • trust proxy matches the real proxy path, verified by checking req.ip in logs.
  • Helmet is enabled with an application-specific CSP that was trialled in report-only mode and has a reporting endpoint.
  • express-session uses a production store, a non-default cookie name, Secure, HttpOnly, SameSite and a strong rotated secret from a secrets manager.
  • Sessions are regenerated on login and destroyed server-side on logout.
  • Login, reset and token endpoints have account-aware rate limits backed by a shared store.
  • Body parsers have explicit size limits, input is type-checked and schema-validated, and redirects use an allowlist.
  • Authentication is applied at router level with a documented public route list tested in CI.
  • npm ci, npm audit and a lint step with security rules run on every build.

Standards and references

Related NHI Mgmt Group resources: Node.js Security Guide · Security Headers Guide · Cookie Security Guide · Rate Limiting and API Abuse Guide · Server-Side Template Injection (SSTI) Guide

PDF for subscribers

Take this guide with you

Every section, table and checklist in one PDF, with links to the sources and related guides. Subscribe to our weekly NHI & AI Identity Journal and we’ll email you a download link.

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 27 October 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org

PDF Downloads for Newsletter Subscribers

Get the guide

Enter your details and we’ll email you a download link.

The full guide is free to read online. The PDF is a thank you for subscribing.

Fill in all fields marked * and tick the PDF box to continue.