Book Image

MEAN Blueprints

By : Robert Onodi
Book Image

MEAN Blueprints

By: Robert Onodi

Overview of this book

The MEAN stack is a combination of the most popular web development frameworks available—MongoDB, Angular, Express, and Node.js used together to offer a powerful and comprehensive full stack web development solution. It is the modern day web dev alternative to the old LAMP stack. It works by allowing AngularJS to handle the front end, and selecting Mongo, Express, and Node to handle the back-end development, which makes increasing sense to forward-thinking web developers. The MEAN stack is great if you want to prototype complex web applications. This book will enable you to build a better foundation for your AngularJS apps. Each chapter covers a complete, single, advanced end-to-end project. You’ll learn how to build complex real-life applications with the MEAN stack and few more advanced projects. You will become familiar with WebSockets and build real-time web applications, as well as create auto-destructing entities. Later, we will combine server-side rendering techniques with a single page application approach. You’ll build a fun project and see how to work with monetary data in Mongo. You will also find out how to a build real-time e-commerce application. By the end of this book, you will be a lot more confident in developing real-time, complex web applications using the MEAN stack.
Table of Contents (13 chapters)
MEAN Blueprints
Credits
About the Author
About the Reviewer
www.PacktPub.com
Preface
Index

Securing your application routes


You probably don't want to let anyone see your contacts, so it's time to secure your endpoints. There are many strategies that we can use to authenticate trusted users in an application. We are going to use a classic, state-full e-mail and password based authentication. This means that the session will be stored on the server side.

Remember we discussed at the beginning of the chapter how we are going to store our session on the server side? We choose two integrations, one with default in-memory session management and one that stores sessions in MongoDB. Everything is configurable from the environment configuration file.

When it comes to handling authentication in Node.js, a good go-to module is Passport, which is a piece of authentication middleware. Passport has a comprehensive set of authentication strategies using a simple username-and-password combination for Facebook, Google, Twitter, and many more.

We have already added this dependency to our application and made the necessary initializations in the express configuration file. We still need to add a few things, but before that, we have to create some reusable components in our backend application. We are going to create a helper file that will ease our interactions with passwords.

Describing the password helper

Before we dive deeper into the authentication mechanism, we need to be able to store in MongoDB a password hash instead of the plain password. We want to create a helper for this task that enables us to make operations related to passwords.

Create a new folder in the tests folder, named unit. Add a new file, contact-manager/tests/unit/password.test.js, and then add the following code to it:

'use strict';

const chai = require('chai');
const should = chai.should();
const passwordHelper = require('../../app/helpers/password');

describe('Password Helper', () => {
});

In our main description body, we are going to add segments that represent our features in more detail. Add this code:

describe('#hash() - password hashing', () => {
});
describe('#verify() - compare a password with a hash', () => {
});

Mocha also provides an it() function, which we are going to use to set up a concrete test. The it() function is very similar to describe(), except that we put only what the feature is supposed to do. For assertion, we are going to use the Chai library. Add the following code to the tests/unit/password.test.js file:

  describe('#hash() - password hashing', () => {
    it('should return a hash and a salt from a plain string', (done) => {
      passwordHelper.hash('P@ssw0rd!', (err, hash, salt) => {
        if (err) throw err;

        should.exist(hash);
        should.exist(salt);
        hash.should.be.a('string');
        salt.should.be.a('string');
        hash.should.not.equal('P@ssw0rd!');
        done();
      });
    });

    it('should return only a hash from a plain string if salt is given', (done) => {
      passwordHelper.hash('P@ssw0rd!', 'secret salt', (err, hash, salt) => {
        if (err) throw err;

        should.exist(hash);
        should.not.exist(salt);
        hash.should.be.a('string');
        hash.should.not.equal('P@ssw0rd!');
        done();
      });
    });

    it('should return the same hash if the password and salt ar the same', (done) => {
      passwordHelper.hash('P@ssw0rd!', (err, hash, salt) => {
        if (err) throw err;

        passwordHelper.hash('P@ssw0rd!', salt, function(err, hashWithSalt) {
          if (err) throw err;

          should.exist(hash);
          hash.should.be.a('string');
          hash.should.not.equal('P@ssw0rd!');
          hash.should.equal(hashWithSalt);
          done();
        });
      });
    });
  });

The passwordHelper should also test whether a password matches the given hash and salt combo. For this, we are going to add the following describe method:

  describe('#verify() - compare a password with a hash', () => {
    it('should return true if the password matches the hash', (done) => {
      passwordHelper.hash('P@ssw0rd!', (err, hash, salt) => {
        if (err) throw err;

        passwordHelper.verify('P@ssw0rd!', hash, salt, (err, result) => {
          if (err) throw err;

          should.exist(result);
          result.should.be.a('boolean');
          result.should.equal(true);
          done();
        });
      });
    });

    it('should return false if the password does not matches the hash', (done) => {
      passwordHelper.hash('P@ssw0rd!', (err, hash, salt) => {
        if (err) throw err;

        passwordHelper.verify('password!', hash, salt, (err, result) => {
          if (err) throw err;

          should.exist(result);
          result.should.be.a('boolean');
          result.should.equal(false);
          done();
        });
      });
    });
  });

Implementing the password helper

We will implement our password helper in the following file: contact-manager/app/helpers/password.js.

The first description of our password helper describes a function that creates a hash from a plain password. In our implementation, we will use a key derivation function that will compute a hash from our password, also known as key stretching.

We are going to use the pbkdf2 function from the built-in Node.js crypto library. The asynchronous version of the function takes a plain password and applies an HMAC digest function. We will use sha256 to get a derived key of a given length, combined with a salt through a number of iterations.

We want to use the same hashing function for both cases: when we already have a password hash and a salt and when we have only a plain password. Let's see the final code for our hashing function. Add the following:

'use strict';

const crypto = require('crypto');
const len = 512;
const iterations = 18000;
const digest = 'sha256';

module.exports.hash = hashPassword;
module.exports.verify = verify;

function hashPassword(password, salt, callback) {
  if (3 === arguments.length) {
    crypto.pbkdf2(password, salt, iterations, len, digest, (err, derivedKey) => {
      if (err) {
        return callback(err);
      }

      return callback(null, derivedKey.toString('base64'));
    });
  } else {
    callback = salt;
    crypto.randomBytes(len, (err, salt) => {
      if (err) {
        return callback(err);
      }

      salt = salt.toString('base64');
      crypto.pbkdf2(password, salt, iterations, len, digest, (err, derivedKey) => {
        if (err) {
          return callback(err);
        }

        callback(null, derivedKey.toString('base64'), salt);
      });
    });
  }
}

Let's see what we get if we run our tests now. Run the following command:

$ mocha tests/unit/password.test.js

The output should be similar to this:

  Password Helper
    #hash() - password hashing
      √ should return a hash and a salt from a plain string (269ms)
      √ should return only a hash from a plain string if salt is given (274ms)
      √ should return the same hash if the password and salt are the same (538ms)

3 passing (2s)

As you can see, we have successfully implemented our hashing function. All the requirements from the test case have passed. Notice that it takes up to 2 seconds to run the tests. Don't worry about this; it's because of the key stretching function taking time to generate the hash from the password.

Next, we are going to implement the verify() function, which checks whether a password matches an existing user's password-hash-and-salt combination. From the description in our tests, this function accepts four parameters: the plain password, a hash that was generated using the third salt parameter, and a callback function.

The callback gets two arguments: err and result. The result can be true or false. This will reflect whether the password matches the existing hash or not. Considering the constraints from the tests and the preceding explanation, we can append the following code to our password.helpr.js file:

function verify(password, hash, salt, callback) {
  hashPassword(password, salt, (err, hashedPassword) => {
    if (err) {
      return callback(err);
    }

    if (hashedPassword === hash) {
      callback(null, true);
    } else {
      callback(null, false);
    }
  });
}

By now, we should have implemented all the specifications from our tests.

Creating the user Mongoose schema

In order to grant access to users in the application, we need to store them in a MongoDB collection. We'll create a new file called contact-manager/app/models/user.model.js and add the following code:

'use strict';

const mongoose = require('mongoose');
const passwordHelper = require('../helpers/password');
const Schema = mongoose.Schema;
const _ = require('lodash');

var UserSchema = new Schema({
  email:  {
    type: String,
    required: true,
    unique: true
  },
  name: {
    type: String
  },
  password: {
    type: String,
    required: true,
    select: false
  },
  passwordSalt: {
    type: String,
    required: true,
    select: false
  },
  active: {
    type: Boolean,
    default: true
  },
  createdAt: {
    type: Date,
    default: Date.now
  }
});

The following table gives a description of the fields in the schema:

Field

Description

email

The e-mail of the user. This is used to identify the user. E-mails will be unique in the system.

name

The full name of the user.

password

This is the password provided by the user. It will not be stored in plaintext in the database but in a hashed form instead.

passwordSalt

Every password will be generated using a unique salt for the given user.

active

This specifies the state of the user. It can be active or inactive.

createdAt

The date when the user was created.

Describing the authentication method from the user model

We'll describe a user authentication method. It will check whether a user has valid credentials. The following file, contact-manager/tests/integration/user.model.test.js, should contain all the test cases regarding the User model. These lines of code will test the authenticate() method:

  it('should authenticate a user with valid credentials', done => {
    User.authenticate(newUserData.email, newUserData.password, (err, user) => {
      if (err) throw err;

      should.exist(user);
      should.not.exist(user.password);
      should.not.exist(user.passwordSalt);
      user.email.should.equal(newUserData.email);
      done();
    });
  });

  it('should not authenticate user with invalid credentials', done => {
    User.authenticate(newUserData.email, 'notuserpassowrd', (err, user) => {
      if (err) throw err;

      should.not.exist(user);
      done();
    });
  });

Implementing the authentication method

Mongoose lets us add static methods to compiled models from schemas. The authenticate() method will search for a user in the database by its e-mail and use the password helper's verify() function to check whether the sent password is a match.

Add the following lines of code to the contact-manager/app/models/user.js file:

UserSchema.statics.authenticate = authenticateUser;

function authenticateUser(email, password, callback) {
  this
  .findOne({ email: email })
  .select('+password +passwordSalt')
  .exec((err, user) => {
    if (err) {
      return callback(err, null);
    }

    // no user found just return the empty user
    if (!user) {
      return callback(err, user);
    }

    // verify the password with the existing hash from the user
    passwordHelper.verify(
      password,
      user.password,
      user.passwordSalt,
      (err, result) => {
        if (err) {
          return callback(err, null);
        }

        // if password does not match don't return user
        if (result === false) {
          return callback(err, null);
        }

        // remove password and salt from the result
        user.password = undefined;
        user.passwordSalt = undefined;
        // return user if everything is ok
        callback(err, user);
      }
    );
  });
}

In the preceding code, when selecting the user from MongoDB, we explicitly selected the password and passwordSalt fields. This was necessary because we set the password and passwordSalt fields to not be selected in the query result. Another thing to note is that we want to remove the password and salt from the result when returning the user.

Authentication routes

In order to authenticate in the system we are building, we need to expose some endpoints that will execute the necessary business logic to authenticate a user with valid credentials. Before jumping into any code, we are going to describe the desired behavior.

Describing the authentication routes

We are only going to take a look at a partial code from the integration test of the authentication functionality, found in contact-manager/tests/integration/authentication.test.js. It should look something like this:

  describe('Sign in user', () => {
    it('should sign in a user with valid credentials', (done) => {
      request({
        method: 'POST',
        url: baseUrl + '/auth/signin',
        form: {
          'email': userFixture.email,
          'password': 'P@ssw0rd!'
        },
        json:true
      }, (err, res, body) => {
        if (err) throw err;

        res.statusCode.should.equal(200);
        body.email.should.equal(userFixture.email);
        should.not.exist(body.password);
        should.not.exist(body.passwordSalt);
        done();
      });
    });

    it('should not sign in a user with invalid credentials', (done) => {
      request({
        method: 'POST',
        url: baseUrl + '/auth/signin',
        form: {
          'email': userFixture.email,
          'password': 'incorrectpassword'
        },
        json:true
      }, (err, res, body) => {
        if (err) throw err;

        res.statusCode.should.equal(400);
        body.message.should.equal('Invalid email or password.');
        done();
      });
    });
  });

So, we've described an auth/signin endpoint; it will authenticate a user using an e-mail-and-password combination. We are testing two scenarios. The first one is when a user has valid credentials and the second is when an incorrect password is sent.

Integrating Passport

We mentioned Passport earlier in the chapter and added some basic logic for this purpose, but we still need to make a proper integration. The Passport module should already be installed and the session management is already in place. So next, we need to create a proper configuration file, contact-manager/config/passport.js, and add the following:

'use strict';

const passport = require('passport');
const mongoose = require('mongoose');
const User = mongoose.model('User');

module.exports.init = initPassport;

function initPassport(app) {
  passport.serializeUser((user, done) => {
    done(null, user.id);
  });

  passport.deserializeUser((id, done) => {
    User.findById(id, done);
  });

  // load strategies
  require('./strategies/local').init();
}

For each subsequent request, we need to serialize and deserialize the user instance to and from the session. We are only going to serialize the user's ID into the session. When subsequent requests are made, the user's ID is used to find the matching user and restore the data in req.user.

Passport gives us the ability to use different strategies to authenticate our users. We are only going to use e-mail and password to authenticate a user. To keep everything modular, we are going to move the strategies into separate files. The so-called local strategy, which will be used to authenticate users using an e-mail and a password, is going to be in the contact-manager/config/strategies/local.js file:

'use strict';

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

module.exports.init = initLocalStrategy;

function initLocalStrategy() {
  passport.use('local', new LocalStrategy({
      usernameField: 'email',
      passwordField: 'password'
    },
    (email, password, done) => {
      User.authenticate(email, password, (err, user) => {
        if (err) {
          return done(err);
        }

        if (!user) {
          return done(null, false, { message: 'Invalid email or password.' });
        }

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

Implementing the authentication routes

Now that we have passport up and running, we can define our authentication controller logic and a proper route to sign in users. Create a new file called contact-manager/app/controllers/authentication.js:

'use strict';

const passport = require('passport');
const mongoose = require('mongoose');
const User = mongoose.model('User');

module.exports.signin = signin;

function signin(req, res, next) {
  passport.authenticate('local', (err, user, info) => {
    if (err) {
      return next(err);
    }

    if (!user) {
      return res.status(400).send(info);
    }

    req.logIn(user, (err) => {
      if (err) {
        return next(err);
      }

      res.status(200).json(user);
    });
  })(req, res, next);
}

Here, we use the .authenticate() function from Passport to check a user's credentials using the local strategy implemented earlier. Next, we are going to add the authentication route, create a new file called contact-manager/app/routes/auth.js, and add the following lines of code:

'use strict';

var express = require('express');
var router = express.Router();
var authCtrl = require('../controllers/authentication');

router.post('/signin', authCtrl.signin);
router.post('/register', authCtrl.register);

module.exports = router;

Note that we skipped the register user functionality, but don't worry! The final bundled project source code will have all of the necessary logic.

Restricting access to contacts routes

We created all the requirements to authenticate our users. Now it's time to restrict access to some of the routes, so technically we are going to create a simple ACL. To restrict access, we are going to use a piece of middleware that will check whether users are authenticated or not.

Let's create our middleware file, contact-manager/app/middlewares/authentication.js. This should contain these lines of carefully crafted code:

'use strict';

module.exports.ensured = ensureAuthenticated;

function ensureAuthenticated(req, res, next) {
  if (req.isAuthenticated()) {
    return next();
  }

  res.status(401).json({
    message: 'Please authenticate.'
  });
}

We have already added the necessary logic to restrict users to the contact routes; that was when we first created them. We succeeded in adding all the necessary pieces of code to manage contacts and restrict access to our endpoints. Now we can continue and start building our Angular 2 application.