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.
- A wall with one gate, not ten open sides.
- A guard who checks ID cards at the gate.
- A rule: only five visitors per hour for the principal.
- 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:
- How security headers protect browsers, with Helmet.
- How CORS works, and why it is not authentication.
- How to stop XSS and CSRF.
- How to limit repeated requests and slow requests.
- How to keep secrets safe.
- 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 withapp.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 proxyso Express knows. See Redirects, Content Negotiation and Proxies. - Use
securecookies, 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
OPTIONSrequest the browser sends first, for some requests. - Install:
npm install cors. Thenapp.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
Originat 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
Authorizationheader instead of cookies are not exposed in the same way.
- Open the form, press Source, and copy the
_csrfvalue. - Send
POST /orderswith bodyitem=Idli&_csrf=<that value>. It works. - The quick request with
WRONGgets 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-limitfor rate limits.
- The 4th try gets 429, even with the right PIN. The guesser is locked out for a while.
- Open Headers to see
RateLimitandRetry-After. - Behind a proxy, set
trust proxycorrectly, 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
.envlocally, and in the host's secret settings in production. - Add
.envto.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/canteento.envand 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:
app.disable("x-powered-by")andapp.set("trust proxy", ...).app.use(helmet()).app.use(cors({ origin: allowlist }))for API routes.- A general
rateLimitfor the whole API, and a strict one for login. - Body parsers with small limits.
- Sessions with
httpOnly,secureandsameSitecookies. - Authentication and role checks on protected routes.
- Validation on every input. See Input Processing and Validation.
- 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,secureandsameSite. trust proxymatches the real setup.- Error answers are safe, with details only in logs.
- Express and every package are up to date, and
npm auditis clean.
Simple lesson: Review every app against the checklist before it goes live.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
cors() with no options on a private API | CORS protects data | CORS only guides browsers | Any site's scripts can call it |
| Relying on CORS instead of login | Blocked origins cannot get data | curl ignores CORS | Data is open to everyone |
| No rate limit on login | Passwords are hard to guess | Bots try thousands per minute | Accounts get broken into |
helmet() after the routes | Order does not matter | Middleware only affects later routes | No security headers |
| Secrets in the code | The repo is private | Repos leak and get shared | Passwords exposed |
| Same secret in dev and production | One secret is simpler | Separate secrets | A dev leak breaks production |
| Never updating packages | Old versions are stable | Old versions have known holes | Easy, well-known attacks |
Check Yourself
Exercises
What to Remember
helmet()early, andx-powered-byoff.- HTTPS everywhere, with
trust proxyand secure cookies. - CORS with an allowlist. CORS is not authentication.
- Escape output for XSS.
sameSitecookies 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.