When we use an Express app for production, i.e. it’s used by external users, we have to be careful about security since it’s available to the outside world.
In this article, we’ll look at some security best practices when running Express apps in a production environment.
Don’t Use Outdated Versions of Express
Outdated versions of Express are no longer maintained and may have unpatched security vulnerabilities, leaving our apps at risk for all sorts of attacks.
Use TLS
If we’re transporting data that needs to be secured, we should use TLS so that users’ data is protected. This means that we avoid man-in-the-middle attacks and packet sniffing.
Even our apps don’t transport secure data, it still gives users more confidence that our website is secure.
We can get free TLS certificates with Let’s Encrypt. It’s an automated service that generates new certificates that are trusted by browsers,
Use Helmet
Helmet is a series of Express middlewares to protect our apps with some well-known security vulnerabilities on the web by setting HTTP headers.
The middlewares included with Helment includes:
csp
— sets theContent-Security-Policy
header to help prevent cross-site scripting attacks and other cross-site injections.hidePoweredBy
— removes theX-Powered-By
headerhpkp
— adds public key pinning header to prevent man-in-the-middle attacks with forged certificateshsts
— setsStrict-Transport-Security
header that enforces HTTPS connects to the server.ieNoOpen
0 setsX-Download-Options
for IE 8 or laternoCache
— setsCache-Control
andPragma
header to disable client-side cachingnoSniff
— setsX-Content-Type-Options
header to prevent browsers from MIME-sniffing a response away from the declared content typeframeguard
— sets theX-Frame-Options
header to provide clickjacking protectionxssFilter
— setsX-XSS-Protection
to enable cross-site scripting filter in most recent web browsers
It’s a good idea to at least disable the x-powered-by
response header so that attackers won’t know that our app is an Express app and launch specific attacks for it.
Use Cookies Securely
We can use the express-session
or cookie-session
middleware to use cookies securely.
express-session
stores session data on the server. It only saves the session ID on the cookie itself.
We can set up a session store for production use since it uses an in-memory store by default.
cookie-session
stores the whole cookie on the client-side. We shouldn’t exceed 4093 bytes when we set our cookies.
The cookie data will be visible to the client, so secret data shouldn’t be sent to the client with cookie-session
.
Don’t Use Default Session Cookie Name
The default session cookie name set by Express shouldn’t be used since it can identify that an app is running on Express.
We can do this by passing in an object to session
as follows:
const express = require('express');
const bodyParser = require('body-parser');
const session = require('express-session');
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.set('trust proxy', 1);
app.use(session({
secret: 'secret',
name: 'sessionId'
}))
app.listen(3000);
Set Cookie Security Options
To enhance security, we can set various cookie options. The following options are available:
secure
— ensures browsers only send cookie over HTTPShttpOnly
— ensures cookie is sent only over HTTP(S) and not client-side JavaScript. This helps us prevent XSS attacksdomain
— indicates the domain of the cookie, which can be used to compare against the domain of the server in the URL that’s requesting the cookie.path
— indicates the path to compare against the requested path, then send the cookie in the requestexpires
— set the expiration date for persistent cookies.
For example, we can use it as follows:
const express = require('express');
const bodyParser = require('body-parser');
const session = require('express-session');
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const expiryDate = new Date(Date.now() + 60 * 60 * 1000)
app.use(session({
name: 'session',
secret: 'secret',
keys: ['foo', 'bar'],
cookie: {
secure: true,
httpOnly: true,
domain: 'EnormousBeneficialScript--five-nine.repl.co',
path: '/',
expires: expiryDate,
}
}));
app.get('/', (req, res) => {
req.session.foo = 'foo';
res.send(req.session.foo);
})
app.listen(3000);
Prevent Brute-Force Attacks
We can rate limit our authorization routes so that attackers can’t use brute-force attacks to guess the credentials stored in our app’s database.
For example, we can limit by IP address by limiting the number of failed attempts that people can make if they have the same user name and IP address.
Then we lock out the user for some time if the number of failed attempts what we can tolerate.
If more than 100 failed attempts in a day are made then we block the IP address.
We can use the rate-limiter-flexible package to do rate-limiting of our routes.
Conclusion
To keep our Express app secure in a production environment, we should take a few precautions.
First, we should use TLS for transporting data to keep them from being sniffed and prevent man-in-the-middle attacks.
Next, we should set headers in ways that prevent users from finding information about our app and to enforce secure communication.
Also, we should use cookies securely by signing it with a secret and set the security options for them.
Finally, to prevent brute-force attacks, we should set a rate limit for our API calls so that attackers can’t guess our users’ credentials by repeated login attempts.