Secrets in source code are the most common cause of leaked credentials. HTTPS is the only thing that keeps those credentials and your users' data private in transit. Both are configuration problems, and both are solved once for the whole app.
Learning objectives
- Explain what TLS protects and what it does not.
- Redirect HTTP to HTTPS and enable HSTS.
- Set security headers with sensible defaults.
- Manage secrets with
.env, validation and rotation.
1. What HTTPS gives you
TLS encrypts the connection between browser and server, verifies the server's identity through a certificate, and prevents tampering in transit. Without it:
- Passwords and session cookies travel in plain text.
- A public Wi-Fi network can inject scripts into your pages.
- Browsers mark the site "Not secure" and some APIs refuse to work.
HTTPS does not protect data at rest, authorise users, or stop XSS. It protects the wire.
Hosting: Cloudflare, Vercel and most platforms provision and renew certificates automatically. Force HTTPS at the edge and at the app.
2. Redirect and HSTS
app.use((req, res, next) => {
if (process.env.NODE_ENV === "production" && req.protocol !== "https") {
return res.redirect(301, `https://${req.headers.host}${req.originalUrl}`);
}
next();
});
If TLS terminates at a proxy, trust the proxy so req.protocol is accurate:
app.set("trust proxy", 1);
HSTS tells the browser to use HTTPS for this domain for a period — even if a link says http://:
Strict-Transport-Security: max-age=31536000; includeSubDomains
Only enable HSTS once HTTPS works on every subdomain; it is difficult to undo. Add preload only when you are certain.
3. Security headers
app.use((req, res, next) => {
res.setHeader("X-Content-Type-Options", "nosniff");
res.setHeader("X-Frame-Options", "DENY");
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
res.setHeader("Permissions-Policy", "camera=(), microphone=(), geolocation=()");
res.setHeader(
"Content-Security-Policy",
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'none'",
);
next();
});
helmet packages these defaults if you prefer a dependency. Check each header against your actual needs — a CSP that breaks your own scripts gets removed by the next developer unless it is tuned.
4. Secrets
# .env — never committed
PORT=3000
NODE_ENV=production
DATABASE_URL=postgres://app:password@db:5432/app
SESSION_SECRET=64-char-random-hex
JWT_SECRET=another-64-char-random-hex
require("dotenv").config();
const required = ["DATABASE_URL", "SESSION_SECRET", "JWT_SECRET"];
const missing = required.filter((key) => !process.env[key]);
if (missing.length > 0) {
console.error(`Missing environment variables: ${missing.join(", ")}`);
process.exit(1);
}
Fail fast at startup. A server that boots with an empty secret is worse than one that refuses to start.
Generate secrets with a CSPRNG:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
5. Handling leaked secrets
- Revoke the key at the provider immediately.
- Generate a new one and deploy it through the environment.
- Remove it from the working tree and from Git history (BFG or
git filter-repo). - Assume it was used. Check logs for unusual access.
- Add a secret scanner (GitHub secret scanning, gitleaks) to CI.
Deleting the file in a new commit does not remove it from history.
6. Production configuration
- Set
NODE_ENV=production(Express disables stack traces). - Enable
app.set("trust proxy", 1)behind a load balancer. - Use a session store (Redis) instead of the default memory store.
- Rotate secrets on a schedule and after any staff change.
- Never log request bodies, tokens or cookies.
Common mistakes
- Committing
.envand only deleting it later. secure: falsecookies in production "because login did not work on HTTP".- HSTS with
preloadon a domain that still has HTTP-only subdomains. - Logging full request headers, including
Authorization. - One shared secret for sessions, JWT and API keys.
Practice
Harden the Express app in the assignment: validate env vars, set headers, force HTTPS in production and produce a clean .env.example.