Express Security

By Syncfusion®, Inc. · Last updated

Security headers with Helmet, HTTPS, CORS and why it is not authentication, XSS and CSRF, rate limiting, request timeouts, keeping secrets safe, the order of security middleware, and a security review checklist.

What you will learn

  • Add security headers with Helmet
  • Configure CORS with an allowlist
  • Defend against XSS and CSRF
  • Rate limit logins and time out slow requests
  • Keep secrets out of code, logs and Git

Express Security Introduction

A server on the internet is visited by friends and attackers alike.

  • Security = making sure only the right people can do the right things.
  • Most attacks use simple mistakes: missing headers, trusted input, weak limits.
  • No single package makes an app safe. Many small layers do.
  • Express gives you the building blocks. You choose and order them.
  • This lesson brings together the safety ideas from earlier lessons.

Simple lesson: Security is many small layers, each stopping a different attack.

A Real-World Example

The school protects its building in layers.

  1. A wall with one gate, not ten open sides.
  2. A guard who checks ID cards at the gate.
  3. A rule: only five visitors per hour for the principal.
  4. Lockers with keys, and the master key kept in the office safe.

If one layer fails, the next one still helps. Your Express app works the same way.

In this lesson, you will learn:

  1. How security headers protect browsers, with Helmet.
  2. How CORS works, and why it is not authentication.
  3. How to stop XSS and CSRF.
  4. How to limit repeated requests and slow requests.
  5. How to keep secrets safe.
  6. A checklist to review any Express app.

Security Headers with Helmet

Browsers follow safety rules that servers turn on with headers.

  • Helmet = middleware that sets a safe group of headers. Install: npm install helmet.
  • Content-Security-Policy (CSP): which scripts, styles and images a page may load.
  • Strict-Transport-Security (HSTS): "always use HTTPS for this site".
  • X-Content-Type-Options: nosniff: browsers must trust your Content-Type.
  • X-Frame-Options: stops other sites from showing your page in a frame (clickjacking).
  • Referrer-Policy: limits what other sites learn about your URLs.
  • Helmet also removes X-Powered-By. You can do that alone with app.disable("x-powered-by").
  • Open Headers for both answers and compare.
  • The CSP now allows images from the school's image server, as configured.
  • In real apps, put app.use(helmet()) near the top, for every route.

Helmet's default Content-Security-Policy is strict. If your pages load scripts or images from other sites, add those sites to the matching directive, as the lab does for images.

Simple lesson: app.use(helmet()) early turns on many browser protections at once.

HTTPS

  • HTTPS encrypts everything between the browser and the server.
  • Without it, anyone on the same Wi-Fi can read passwords and cookies.
  • Usually a proxy or your host provides HTTPS, not Express itself.
  • Set trust proxy so Express knows. See Redirects, Content Negotiation and Proxies.
  • Use secure cookies, so they only travel over HTTPS. See Cookies and Sessions.

Simple lesson: Real servers always use HTTPS. Express should know it is behind one.

Cross-Origin Resource Sharing (CORS)

Browsers block a web page from reading answers from another origin, unless that server allows it.

  • Origin = protocol + host + port, such as https://canteen.school.in.
  • CORS = headers the server sends to say "this other origin may read my answers".
  • Preflight = an OPTIONS request the browser sends first, for some requests.
  • Install: npm install cors. Then app.use(cors({ origin: [...] })).
  • Use an allowlist of origins, not "*", for anything private.
  • credentials: true lets cookies travel. It cannot be used with "*".
  • The school's origin gets Access-Control-Allow-Origin. The evil origin does not.
  • But look: the evil origin still got the data. Only a browser would hide it.
  • The last request has no Origin at all, like curl. It also gets the data.

CORS is not authentication. It only tells browsers what to show to web pages. curl, Postman and scripts ignore it. Protect private routes with a login check.

Simple lesson: CORS controls browsers. Login checks control access.

Cross-Site Scripting (XSS)

XSS = an attacker's script runs on your page, in your users' browsers.

  • It happens when user text is put into HTML without escaping.
  • Escape output: <%= %> in EJS, never <%- %> for user data. See Views and Static Files.
  • Return JSON from APIs, with Content-Type: application/json.
  • A strict Content-Security-Policy blocks injected inline scripts as a second layer.
  • Mark important cookies httpOnly, so a script cannot steal them.

Simple lesson: Escape every user value in HTML, and let CSP catch what slips through.

Cross-Site Request Forgery (CSRF)

CSRF = another site tricks a logged-in user's browser into sending a request to your site.

  • The browser sends your cookies along, so the request looks real.
  • sameSite: "lax" or "strict" cookies stop most of these. Use them.
  • For extra safety on forms, use a CSRF token: a secret in the session that each form must send back.
  • A request without the right token is rejected with 403.
  • APIs that use an Authorization header instead of cookies are not exposed in the same way.
  • Open the form, press Source, and copy the _csrf value.
  • Send POST /orders with body item=Idli&_csrf=<that value>. It works.
  • The quick request with WRONG gets 403, like an attacker's forged form.

Simple lesson: sameSite cookies first, CSRF tokens for forms that change data.

Abuse Prevention

Some attacks just send too many requests, or very slow ones.

  • Rate limiting = at most N requests per time window per client. Too many get 429.
  • Brute-force protection = a strict limit on login routes, so passwords cannot be guessed.
  • Request slowdown = add delay after many requests, with a package like express-slow-down.
  • Request timeouts = give up on requests that take too long, and answer 503.
  • Body size limits = small limits on parsers. See Body Parsers and Third-Party Middleware.
  • Install express-rate-limit for rate limits.
  • The 4th try gets 429, even with the right PIN. The guesser is locked out for a while.
  • Open Headers to see RateLimit and Retry-After.
  • Behind a proxy, set trust proxy correctly, or every user shares one limit.

Simple lesson: Limit how often, how big and how long. Login routes get the strictest limits.

Keeping Secrets Safe

Secret = a value that gives power: database passwords, session secrets, API keys.

  • Keep secrets in .env locally, and in the host's secret settings in production.
  • Add .env to .gitignore. Commit only .env.example. See Introduction to Express.js.
  • Use different secrets for development and production.
  • Check at startup: stop the server if a required secret is missing.
  • Never log secrets, and never send them in answers.
  • If a secret leaks, change it at once. Deleting the commit is not enough.
  • The server stops, naming the missing setting. Better now than a strange bug later.
  • Add DATABASE_URL=postgres://localhost/canteen to .env and run again.

Easy way to remember: secrets live in the environment, never in the code, the logs or Git.

Simple lesson: Load secrets from the environment, check them at startup, and never print them.

Putting It Together

A typical order for security middleware:

  1. app.disable("x-powered-by") and app.set("trust proxy", ...).
  2. app.use(helmet()).
  3. app.use(cors({ origin: allowlist })) for API routes.
  4. A general rateLimit for the whole API, and a strict one for login.
  5. Body parsers with small limits.
  6. Sessions with httpOnly, secure and sameSite cookies.
  7. Authentication and role checks on protected routes.
  8. Validation on every input. See Input Processing and Validation.
  9. Error middleware that never leaks details. See Error Handling.

Keep packages updated: npm audit lists known problems, and npm audit fix updates safe versions.

Simple lesson: Global protections first, route-level checks next, safe errors last.

Security Review Checklist

Input and Output

  • Every input is validated: params, query, body and headers.
  • Every user value in HTML is escaped.
  • Body parsers have size limits.
  • File uploads are checked and stored outside public/.
  • Redirect targets are checked.

Access and Configuration

  • Login and admin routes are protected and rate limited.
  • Cookies are httpOnly, secure and sameSite.
  • trust proxy matches the real setup.
  • Error answers are safe, with details only in logs.
  • Express and every package are up to date, and npm audit is clean.

Simple lesson: Review every app against the checklist before it goes live.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
cors() with no options on a private APICORS protects dataCORS only guides browsersAny site's scripts can call it
Relying on CORS instead of loginBlocked origins cannot get datacurl ignores CORSData is open to everyone
No rate limit on loginPasswords are hard to guessBots try thousands per minuteAccounts get broken into
helmet() after the routesOrder does not matterMiddleware only affects later routesNo security headers
Secrets in the codeThe repo is privateRepos leak and get sharedPasswords exposed
Same secret in dev and productionOne secret is simplerSeparate secretsA dev leak breaks production
Never updating packagesOld versions are stableOld versions have known holesEasy, well-known attacks

Check Yourself

Exercises

What to Remember

  • helmet() early, and x-powered-by off.
  • HTTPS everywhere, with trust proxy and secure cookies.
  • CORS with an allowlist. CORS is not authentication.
  • Escape output for XSS. sameSite cookies and tokens for CSRF.
  • Rate limit, slow down, time out and cap body sizes.
  • Secrets live in the environment, are checked at startup and never logged.

Next: Deploying an Express App. You will run your app in production and shut it down safely.