# Express Security

Course: [Express.js](https://www.devtutorials.ai/learn/expressjs) · Section: Operations
Source: https://www.devtutorials.ai/learn/expressjs/operations/express-security

> 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.

## 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

<div className="tscroll">

| 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 |

</div>

## 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.
