Authenticating a MEAN Application with Passport and MongoDB - PART I (BACK END)

elevysi · 24 February 2017 |

A MEAN app is composed of the back and front-end. The back end has the NodeJS application, powered through the Express framework. The front-end is a single-page Angular application that will get the requested route and undertake the right action.
A route is handled by an Angular component; a component has the option to specify a HTML template which will be loaded into the page and shown to the user. What the component does is to query the Express Application for JSON data and load this JSON in the HTML template to be shown within the page.

When it comes to authentication, you need to consider both fronts of the application; the back-end protected resources need to be inaccessible to the public. Therefore, when a protected back-end resource is requested, the single-page application shall react and ask for the user’s credentials. The singe-page application needs to send the credentials to the back-end, receive the response back, and if positive, have a way of making the subsequent requests to protected resources go through by managing a stateless authentication for some time.

Two notions are implied in this scheme:

  1. Authentication of the back end
  2. Stateless authentication of the front-end

The first aspect can be delivered through the passport module; it is a very popular module that handles various authentication schemes. We will focus on the database or local authentication of users.
The second aspect will be delivered through jwt, a json based authentication, popular with stateless authentication, which holds an expiry time stamp. Upon successful authentication, passport will issue a jwt token through which the front-end part will get the authenticated user’s information as well as use it to make subsequent requests to protected resources, for as long as the token remains valid.

The front-end shall show the log in page as a reply to an 401 Unauthorized response status code from the back-end; it also needs to keep the requested resource in memory so as to show it after the authentication goes through.

The NodeJS App obviously needs the basic setting; we need the express framework, the body-parser and the passport module to name a few.
Passport will authenticate our users' data (username and password) against a local database but since our front-end will be sending us a token, we need to decode it so that we can send Passport the data it contains to be authenticated. For this purpose, we need to declare the express-jwt module which handles the decoding of the sent token and evaluates its validity.
To sum up, the following snippet is the back-end server.js which has the declaration of the various modules to be included. 

//server.js
var express = require("express");
var bodyParser = require("body-parser");
var path = require('path');
var fs = require('fs');
 
var passport = require('passport');
 
//For files uploads
var multiparty = require('connect-multiparty'); 
 
require('./models/user');
require('./configs/passport');
 
//For getting the secret
var configs = require("./configs/secret");
 
var jwt = require('express-jwt');
var auth = jwt({
  secret: configs.getSecret(),
  userProperty: 'payload'
});
 
// This route redirects all other requests to angular's index
const appRoute = require("./routes/app"); 
var apiRoute = require("./routes/api");
var usersRoute = require("./routes/users");
 
 
//Connects to Mongo
const dbConfig = require("./configs/database");
dbConfig.dbConnect(); 
 
var app = express ();

//View Engine so that all is given to angular
app.set('views', path.join(__dirname, 'views')); 
app.set('view engine', "ejs");
app.engine("html", require('ejs').renderFile);
 
 
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended : true}));
 
//Set static folder, since there is an index file it will be rendered automatically
app.use(express.static(path.join(__dirname, "client")));
//Folder for files uploads
app.set('uploadsDir', path.join(__dirname, 'uploads')); 
 
app.use(passport.initialize())
 
//Users
//only i can register users
app.post("/api/register", usersRoute.register); 
app.post("/api/login", usersRoute.login);
app.get("/api/profile", auth, usersRoute.profileRead);
app.get("/api/users", auth, usersRoute.list);
 
// This route redirects all other requests to angular's index
app.get('/*', appRoute.index); 
 
const PORT = process.env.port || 3000;
app.listen(PORT, () => {
    console.log("Running on the port " +PORT);
});

Our app relies on a MongoDB database which will be ORM-ing our user model through the Mongoose module. Our user model has properties such as the email, the name, a salt and a hash. The salt value will be used to hash the password into a hash to be stored in the database (through the setpassword method) as well as evaluating if a provided password corresponds to the stored hash value (through the validPassword method). The last method generateJwt generates a jwt token for a valid user, one that includes the user's email, username along with the token expiry information. Please note that one can add as much information as they require to. The posession of a valid jwt token exposes this information to any third party, since well known apis will decode the jwt token. However, a jwt token needs to be authenticated against a value that will be referred below as the secret. The app secret is the only piece of information that can guarantee the validity of the token signature, since it is the same secret that was used to sign it before it was sent to the front-end. In a nushtell, the jwt token has the user's information while the secret acts as a salt value to assert the token validity. It follows a kind of Public Key Infrastructure (PKI) model between the front and the back ends. The issued token is the back-end's public key while the secret, used to sign the token is its private key. Passport, in combination with express-jwt, are the Certificate Authority (CA), responsible for issuing the certificates as well as asserting their validity. A major difference here would be that the message (i.e. user information if any) is not fully protected since it is contained (not in plain but in reversible form) in our identified public key; only a sub-part, namely the signature part of the token, can be considered as a secret.
The user model is implemented as shown below:

//models/user.js
var mongoose = require("mongoose");
var crypto = require("crypto");
var jwt = require("jsonwebtoken");

var secretConfig = require("../configs/secret");

var userSchema = new mongoose.Schema({
  email: {
    type: String,
    unique: true,
    required: true
  },
  name: {
    type: String,
    required: true
  },
  hash: String,
  salt: String
});

userSchema.methods.setPassword = function(password){
  this.salt = crypto.randomBytes(16).toString('hex');
  this.hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64).toString('hex');
};

userSchema.methods.validPassword = function(password) {
  var hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64).toString('hex');
  return this.hash === hash;
};

userSchema.methods.generateJwt = function() {
  var expiry = new Date();
  expiry.setDate(expiry.getDate() + 7);

  return jwt.sign({
    _id: this._id,
    email: this.email,
    name: this.name,
    exp: parseInt(expiry.getTime() / 1000),
  }, secretConfig.getSecret()); // DO NOT KEEP YOUR SECRET IN THE CODE!
};

module.exports = mongoose.model('User', userSchema);

The passport module has to be configured by specifying the authentication strategy which will indicate how the user will be authenticated. Mongoose helps search for the user given a username and if found, the given password is checked against the user’s model validPassword method described above (it reverse-engineer the setPassword method).

//configs/passport.js

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

passport.use(new LocalStrategy({
    usernameField: 'email'
  },
  function(username, password, done) {
    User.findOne({ email: username }, function (err, user) {
      if (err) { return done(err); }
      // Return if user not found in database
      if (!user) {
        return done(null, false, {
          message: 'User not found'
        });
      }
      // Return if password is wrong
      if (!user.validPassword(password)) {
        return done(null, false, {
          message: 'Password is wrong'
        });
      }
      // If credentials are correct, return the user object
      return done(null, user);
    });
  }
));

The server.js listing indicates the route used for authentication, i.e. routes/users.js; this route holds the login export that specifies that Passport will authenticate the user and if successful, user model generates a jwt token and sends it back to the front-end along with a 200 OK response code. If the authentication fails, a 401 Unauthorized response code is sent. Please note that the user.generateJwt method signs the token it issues with the application secret.

//routes/users.js

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

module.exports.login = function(req, res) {
  passport.authenticate('local', function(err, user, info){
    var token;

    // If Passport throws/catches an error
    if (err) {
      res.status(404).json(err);
      return;
    }

    // If a user is found
    if(user){ 
      token = user.generateJwt();
      res.status(200);
      res.json({
        "token" : token,
        "success" : true
      });
    } else {
      // If user is not found
      res.status(401).json(info);
    }
  })(req, res);

};

module.exports.register = function(req, res) {
  var user = new User();

  user.name = req.body.name;
  user.email = req.body.email;

  user.setPassword(req.body.password);

  user.save(function(err) {
    var token;
    token = user.generateJwt();
    res.status(200);
    res.json({
      "token" : token,
      "success" : true
    });
  });
};


module.exports.list = function(req, res) {
  User.find({}, (err, resources) => {
        if(err) res.send(err).status(404);
        else {
            // console.log(resources);
            res.send(resources).status(200);
        }
    });
};

The back-end issues a token which will be sent to the front-end; it also has the methods for checking the token when it comes back from the front-end.

You may want to check this post's part ii which describes how the token is handled in the MEAN app front-en, cope this project's code on github, or view the results of the series on the site gram.