Shoulder.dev Logo Shoulder.dev

Implementing Security Measures in benhall/express-demo

Scenario: You have built a web application using the benhall/express-demo project and now want to secure it against common threats. In this guide, we will cover implementing authentication and authorization, encrypting sensitive data, and validating user input.

Solution:

  1. Authentication and Authorization:

First, let's secure our application by implementing authentication and authorization using middleware. We will use Passport.js, an Express-compatible authentication middleware for Node.js.

a. Install Passport.js:

Add the following line to your package.json file under dependencies:

"passport": "^0.4.2"

b. Create a new file app.js in the routes directory and set up Passport.js:

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

// User model
const User = require('../models/user');

// Configure Passport.js
passport.use(new LocalStrategy({ usernameField: 'email' },
  async function(email, password, done) {
    const user = await User.findOne({ email: email });
    if (!user) return done(null, false, { message: 'Incorrect email.' });

    const isValidPassword = await bcrypt.compare(password, user.password);
    if (!isValidPassword) return done(null, false, { message: 'Incorrect password.' });

    return done(null, user);
  }
));

passport.serializeUser(function(user, done) {
  done(null, user.id);
});

passport.deserializeUser(async function(id, done) {
  const user = await User.findById(id);
  done(null, user);
});

// Create a new Express router
const router = express.Router();
router.use(passport.initialize());
router.use(passport.session());

// Protect routes
router.get('/protected', isAuthenticated, function(req, res) {
  res.send('Welcome to the protected section!');
});

// Define middleware
function isAuthenticated(req, res, next) {
  if (req.isAuthenticated()) return next();

  res.redirect('/login');
}

module.exports = router;
  1. Encrypting Sensitive Data:

To encrypt sensitive data, we will use the bcryptjs library. Update your package.json file under dependencies to include it:

"bcryptjs": "^2.4.3"

Modify the User model in models/user.js to hash the password before saving it to the database:

const bcrypt = require('bcryptjs');

// ...

const UserSchema = new mongoose.Schema({
  email: {
    type: String,
    required: true,
    unique: true,
    lowercase: true,
    trim: true,
    validate: {
      isEmail: true,
    },
  },
  password: {
    type: String,
    required: true,
    minlength: 6,
  },
}, {
  timestamps: true,
});

UserSchema.pre('save', async function(next) {
  if (!this.isModified('password')) return next();
  const salt = await bcrypt.genSalt(10);
  this.password = await bcrypt.hash(this.password, salt);
  next();
});

module.exports = mongoose.model('User', UserSchema);
  1. Validating User Input:

To validate user input, we will use Helmet.js, a set of middleware functions to help secure Express.js web applications by setting various HTTP headers. Install Helmet.js by adding it to your package.json file under dependencies:

"helmet": "^4.1.2"

Update your app.js file to include Helmet.js:

const helmet = require('helmet');

// ...

app.use(helmet());

Create a new file app.js in the routes directory and set up Helmet.js:

const helmet = require('helmet');

// Configure Helmet.js
app.use(helmet());

// Define middleware
function xssFilter() {
  return function(req, res, next) {
    res.set('X-XSS-Protection', '1; mode=block');
    next();
  };
}

// Apply middleware
app.use(xssFilter);

Tests:

To verify the security measures implemented, you can perform the following tests:

  1. Authentication and Authorization:

    • Attempt to access a protected route without being authenticated.
    • Attempt to access a protected route with incorrect credentials.
    • Attempt to access a protected route with valid credentials.
  2. Encrypting Sensitive Data:

    • Attempt to retrieve a user's password from the database.
    • Attempt to modify a user's password and verify it is encrypted in the database.
  3. Validating User Input:

    • Attempt to access the application with malicious input in the URL or headers.
    • Attempt to submit malicious input through forms.

These tests should help ensure that the security measures have been implemented correctly and effectively.