Adding an authentication module

To maintain modularity and simplify the authentication process, we will create a separate module to validate the access privileges of a given user.

In your project directory, add the following file named authentication.js. Open the file and insert the following:

var db = require('./database');

module.exports = {
  database: 'OrderBase',
  collection: 'AccessTokens',
  generateToken: function (user, callback) {
    var token = {
      userID: user._id
    }
  }

  // Persist and return the token
  db.insert(this.database, this.collection, token, function (err, res) {
    if (err) {
      callback(err, null);
    } else {
      callback(null, res);
    }
  });
},
authenticate: function (user, password, callback) {
  if (user.password ==== password) {
    // Create a new token for the user
    this.generateToken(user, function (err, res) {
      callback(null, res);
  });});
  } else {
    callback({
      error: 'Authentication error',
      message: 'Incorrect username or password'
    }, null);
  }
}
}

Next, import the module into your entry module, as follows:

var authentication = require('./authentication');

Creating functions to register and help users log in

We will need to add endpoints to our API for the purpose of both creating and authenticating users who wish to interact with it. In light of what we have done thus far, this is easy to do.

Registering users

We begin by adding a URL endpoint for adding users. This will be very familiar in terms of what we already did when creating the REST API in the previous chapter; all that we are going to do is create a POST method for the user collection. First, add the following utility method:

var insertUser = function (user, req, res) {
  insertResource('OrderBase', 'User', user, function (err, result) {
    res.writeHead(200, {"Content-Type": "application/json"});
    res.end(JSON.stringify(result));
  });
};

Next, modify your router to include the following case statement:

case 'api/users/register':
  if (req.method === 'POST') {
    var body = "";
    req.on('data', function (dataChunk) {
      body += dataChunk;
    });
    req.on('end', function () {

      // Done pulling data from the POST body.
      // Turn it into JSON and proceed to store.
      var postJSON = JSON.parse(body);

      // validate that the required fields exist
      if (postJSON.email
      && postJSON.password
      && postJSON.firstName
      && postJSON.lastName) {
        insertUser(postJSON, req, res);
      } else {
        res.end('All mandatory fields must be provided');
      }
    });
  }
  break;

This is all we need to register users. Registrations can now be handled through a simple POST request to the /api/users/register endpoint.

Enabling users to log in

To enable users to log in via our API, we will need to accomplish the following three things:

  • Make sure that the user exists
  • Make sure that a matching password was provided by the the user
  • Return an access token, which can be used by the user for future access

Luckily, all but the first of the preceding list are taken care of by the authentication module that we designed earlier. All that we need to do is plug it into our router. To do this, we will also need to design a new endpoint for the login part.

Add the following case to your router configuration:

case 'api/users/login':
  if (req.method === 'POST') {
    var body = "";
    req.on('data', function (dataChunk) {
      body += dataChunk;
    });
    req.on('end', function () {

      var postJSON = JSON.parse(body);

      // make sure that email and password have been provided
      if (postJSON.email && postJSON.password) {
        findUserByEmail(postJSON.email, function (err, user) {
          if (err) {
            res.writeHead(404, {"Content-Type": "application/json"});
            res.end({
              error: "User not found",
              message: "No user found for the specified email"
            });
          } else {
            // Authenticate the user
            authenticator.authenticate(
            user, postJSON.password, function(err, token) {
              if(err) {
                res.end({
                  error: "Authentication failure",
                  message: "User email and password do not match"
                });
              } else {
                res.writeHead(200, {"Content-Type": "application/json"});
              res.end(JSON.stringify(token));
              }
            });
          }
        });
      });

    } else {
      res.end('All mandatory fields must be provided');
    }
  });
}
  break;

In the preceding code, we added the following simple method in order to handle the looking up of a user by e-mail:

var findUserByEmail = function (email, callback) {
  database.find('OrderBase', 'User', {email: email}, function (err, user) {
    if (err) {
      callback(err, null);
    } else {
      callback(null, user);
    }
  });
};

That's all we need as far as user management is concerned for now. Now, let's add the finishing touch and set up the actual security for our endpoints.

Extending our API

We are now ready to modify our API in order to add the authentication features that we have developed so far. First, let's determine exactly how the access policies should work:

  • Customers should be able to create (insert) orders and retrieve (get) information about products and nothing else
  • Producers should be able to retrieve information about orders and products and also insert new products

We will accomplish this by placing a simple token and role check on each endpoint. The check will simply verify the following:

  • The token is legitimate
  • The user associated with the token has the role that is necessary to perform the requested action

To start, we will add a new function to the authentication module, which will be responsible for checking whether a given token is associated with a given role:

tokenOwnerHasRole: function (token, roleName, callback) {
  var database = this.database;
  db.find(database, 'User', {_id: token.userID}, function (err, user) {
    db.find(database, 'Role', {_id: user.roleID}, function (err, role) {
      if(err){
        callback(err, false);
      }
      else if (role.name ==== roleName) {
        callback(null, true);
      }
      else {
        callback(null, false);
      }
    });
  });
}

This method is all that we need to verify the roles for the token provided (implicitly checking whether the user who owns the token has the specified role).

Next, we simply need to make use of this in our router. For example, let's secure the POST endpoint for our product API. Make it look like the following:

case '/api/products':
  if (req.method === 'GET') {
    // Find and return the product with the given id
    if (parsedUrl.query.id) {
      findProductById(id, req, res);
    }
    // There is no id specified, return all products
    else {
      findAllProducts(req, res);
    }
  }
  else if (req.method === 'POST') {
    var body = "";
    req.on('data', function (dataChunk) {
      body += dataChunk;
    });
    req.on('end', function () {
      var postJSON = JSON.parse(body);

      // Verify access rights
      getTokenById(postJSON.token, function (err, token) {
        authenticator.tokenOwnerHasRole(token, 'PRODUCER', function (err, result) {
          if (result) {
            insertProduct(postJSON, req, res);
          } else {
          res.writeHead(403, {"Content-Type": "application/json"});
            res.end({
              error: "Authentication failure",
              message: "You do not have permission to perform that action"
            });
          }
        });
      });
    });
  }
  break;

That's it! Implementation for the other endpoints is the same, and we will provide you with the full example source code for them.

Though I have covered some basics here, security remains one of the largest and most diverse areas of contemporary software development. We believe that token-based authentication will address a majority of the cases that you are bound to come across in your career. I would like to offer some suggestions for future study as well as complements to the topics that you have studied here.

OAuth

One of the most common authentication standards offered by modern web apps is OAuth (Open Authentication Standard), its second version (OAuth2) in particular. OAuth makes heavy use of access tokens and is used by (among others) Facebook, Google, Twitter, Reddit, and StackOverflow. Part of what makes the standard powerful is that it allows users to sign in with their Google or Facebook accounts, or even some other account that supports OAuth2, when using your services.

There are several mature NPM packages for using OAuth2 with Node.js. In particular, we recommend you to study the node-oauth2-server package (https://github.com/thomseddon/node-oauth2-server).

Time-stamped access tokens

To keep things simple and focus on the main concepts, we have allowed our access tokens in this example to be permanent. This is a very bad security practice since tokens, like passwords, can be compromised and used to grant unauthorized users access to the system.

A common way to reduce this danger is to impose a Time To Live (TTL) value on each access token, indicating how long the token can be used until the user has to authenticate themselves again in order to get a new token.

Hashing passwords

For the sake of simplicity, we allowed passwords in this example to be stored and retrieved as plain text. Needless to say, this is an abysmal security practice and nothing that you should ever do on a production server. Mature Node.js frameworks such as Express.js provide built-in mechanisms for hashing passwords, and you should always choose those when available. In the event that you need to hash passwords on your own, choose the bcrypt module in order to both hash and compare. Here's an example of the same:

var bcrypt = require('bcrypt');

var userPlaintextPassword = "ISecretlyLoveUnicorns";
var userHashedPassword = "";

// First generate a salt value to hash the password with
bcrypt.genSalt(10, function(err, salt) {
  // Hash the password using the salt value
  bcrypt.hash(userPlaintextPassword, salt,
  function(err, hashedPassword) {
    // We now have a fully hashed password
    userHashedPassword = hashedPassword;
  });
});

// Use the same module to compare the hashed password with potential
//matches.
bcrypt.compare("ISecretlyLoveUnicorns", userHashedPassword,
  function(err, result) {
    // Result will simply be true if hashing succeeded.
  });
bcrypt.compare("ISecretlyHateUnicorns", userHashedPassword,
  function(err,  result) {
    // result will be false if the comparison fails
});
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.138.202