DEV Community

Tristan Kalos
Tristan Kalos

Posted on

Express.js Security Best Practices

Express.js is a fast, unopinionated, minimalist web framework for Node.js. It's widely used to build web applications, and as such, ensuring that applications built with Express.js are secure is paramount. Here, we’ll walk through ten best practices to help you strengthen the security of your Express.js applications.

1. Use Helmet

Helmet helps secure your Express apps by setting various HTTP headers. It's not a silver bullet, but it can help protect against some well-known web vulnerabilities by setting headers like X-Content-Type-Options, X-DNS-Prefetch-Control, and others.

const helmet = require('helmet');
const app = require('express')();

app.use(helmet());
Enter fullscreen mode Exit fullscreen mode

2. Keep Dependencies Updated

Vulnerabilities in software are discovered regularly. Using outdated packages can leave you susceptible to these issues. Use tools like npm audit or Snyk to identify and update vulnerable dependencies.

npm audit fix
Enter fullscreen mode Exit fullscreen mode

3. Implement Rate Limiting

Rate limiting helps to prevent brute-force attacks by limiting the number of requests users can make within a certain timeframe. For Express.js, you can use the express-rate-limit middleware.

const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
});

app.use(limiter);
Enter fullscreen mode Exit fullscreen mode

4. Sanitize Input

Input sanitization helps prevent injection attacks. It's important to never trust user input and always to sanitize and validate it before processing.

const express = require('express');
const app = express();
const { body, validationResult } = require('express-validator');

app.post('/user',
  body('username').isAlphanumeric(),
  body('email').isEmail(),
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }

    // Proceed with handling request
});
Enter fullscreen mode Exit fullscreen mode

5. Use HTTPS

HTTP traffic is unencrypted, making it susceptible to interception and alteration. Use HTTPS to encrypt data in transit. With Express.js, you can use Node's https module in conjunction with fs to read SSL/TLS certificates.

const https = require('https');
const fs = require('fs');
const app = require('express')();

const options = {
  key: fs.readFileSync('key.pem'),
  cert: fs.readFileSync('cert.pem')
};

https.createServer(options, app).listen(443);
Enter fullscreen mode Exit fullscreen mode

6. Handle Errors Properly

Express.js has default error handling, but it's recommended to implement a custom error handling middleware to catch and properly manage errors.

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});
Enter fullscreen mode Exit fullscreen mode

7. Use Cookies Securely

If you’re using cookies for session management, use secure + httpOnly flags to prevent client-side scripts from accessing the cookie.

const session = require('express-session');

app.use(session({
  secret: 'your-secret',
  cookie: {
    secure: true,
    httpOnly: true,
  },
  // other configuration
}));
Enter fullscreen mode Exit fullscreen mode

8. Avoid Revealing Stack Traces

Express.js, by default, will expose a stack trace to the client on an error. Turn off stack traces in production to avoid revealing too much.

if (app.get('env') === 'production') {
  app.use((err, req, res, next) => {
    res.status(500).send('Server Error');
  });
}
Enter fullscreen mode Exit fullscreen mode

9. Secure File Uploads

If your application deals with file uploads, ensure you check the file type, limit the size, and scan for malware.

const multer = require('multer');
const upload = multer({
  dest: 'uploads/',
  limits: { fileSize: 1000000 },
  fileFilter: (req, file, callback) => {
    if (!file.originalname.match(/\.(jpg|jpeg|png|gif)$/)) {
      return callback(new Error('Only image files are allowed!'), false);
    }
    callback(null, true);
  }
});
Enter fullscreen mode Exit fullscreen mode

10. Authenticate & Authorize

Always implement strong authentication and authorization checks. Tools like Passport.js can help you manage these securely.

const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;

passport.use(new LocalStrategy((username, password, done) => {
  // Implement your verification logic
}));
Enter fullscreen mode Exit fullscreen mode

When you follow these best practices, you're not only protecting your application but also safeguarding users' data and trust in your application. Remember, security is an ongoing effort and requires regular code reviews, monitoring, and updates to stay ahead of potential threats 😉

Top comments (0)