Node, Express, Mongoose and Passport.js REST API Authentication

by Didin J. on Apr 10, 2017 Node, Express, Mongoose and Passport.js REST API Authentication

A comprehensive step by step tutorial on how to securing or authenticating Node, Express and Mongoose REST API using Passport.js

This tutorial is about how to securing Node, Express, MongoDB and Mongoose REST API using Passport.js Authentication or log in. In the previous tutorial we were talking about web authentication with Node, Express, Mongoose, MongoDB, and Passport.js, but today we are focusing on securing REST API only with a little different usage of Passport.js.

Table of Contents:

Until now, Passport.js still a robust, flexible, and modular authentication middleware for Node.js environment. So, in this tutorial, you will see a lot of Passport.js domination. Authentication mechanism to generate JSON web token (JWT), all handled by Passport.js. The Express job just handle the API routing, the middleware for accessing the MongoDB database is handled by Mongoose.js.

The flow of this tutorial is very simple, an unauthorized user accessing secure resource will return 403 response. Then the user login using credentials username and password. The success response will return the JWT codes (generated by Passport.js - JWT module) that should put in the Authorization headers in every request to the secure resources. The failure login will return a 401 response. For more clear explanation, you can refer to this sequence diagram.

Node Express Mongoose Passport.js - Sequence Diagram

This is just a simple example of authentication using Node, Express, Mongoose, MongoDB, and Passport.js

You can watch a video tutorial for this article on our YouTube channel. Please like, share, comment, and subscribe to our YouTube channel.

Now, we will involve few Javascript library like "bcrypt-nodejs", "jsonwebtoken" and "passport-jwt". Before we started, please check that you have installed the latest Node.js. In the Terminal or Command Line type this command to check the version of Node.js and NPM.

node -v
npm -v

 


Install Express.js Generator and Create New Express Application

First, we have to install Express.js generator to make application development simple and quicker. Open terminal (OS X or Linux) or Node.js command line (Windows) then type this command.

npm install express-generator -g

Now, create a new Express.js starter application by type this command in your Node projects folder.

express node-rest-auth

That command tells express to generate a new Node.js application with the name "node-rest-auth". Go to the newly created project folder.

cd node-rest-auth

Type this command to install default required Node dependencies.

npm install

Now test your Express server by type this command.

npm start

or

nodemon

You will see this page if Express application generated properly.

Node, Express, Mongoose and Passport.js REST API Authentication - Express home page

This time we have to install all the required libraries and dependencies. Type this commands to install the library and dependencies (mongoose, bcrypt, jsonwebtoken, morgan, passport, passport-jwt).

npm install mongoose bcrypt-nodejs jsonwebtoken morgan passport passport-jwt --save


Configure Node.js and Passport.js Application

We will make separate files for the configuration. For that, create the new folder on the root folder.

mkdir config

Create a configuration file for Database and Passport.js.

touch config/database.js
touch config/passport.js

Open and edit "config/database.js" then add these lines of codes.

module.exports = {
  'secret':'nodeauthsecret',
  'database': 'mongodb://localhost/node-auth'
};

This config holds the database connection parameter and secret for generating a JWT token.

Open and edit "config/passport.js" then add these lines of codes.

var JwtStrategy = require('passport-jwt').Strategy,
    ExtractJwt = require('passport-jwt').ExtractJwt;

// load up the user model
var User = require('../models/user');
var config = require('../config/database'); // get db config file

module.exports = function(passport) {
  var opts = {};
  opts.jwtFromRequest = ExtractJwt.fromAuthHeader();
  opts.secretOrKey = config.secret;
  passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
    User.findOne({id: jwt_payload.id}, function(err, user) {
          if (err) {
              return done(err, false);
          }
          if (user) {
              done(null, user);
          } else {
              done(null, false);
          }
      });
  }));
};

This config is used for getting the user by matching JWT token with token get from the client. This configuration needs to create a User model later.

Now, open and edit "app.js" from the root of the project. Declare required library for initializing with the server by adding these lines of requires.

var morgan = require('morgan');
var mongoose = require('mongoose');
var passport = require('passport');
var config = require('./config/database');

Create a connection to MongoDB.

mongoose.connect(config.database, { useCreateIndex: true, useNewUrlParser: true, useUnifiedTopology: true });

Declare a variable for API route.

var api = require('./routes/api');

To make this server CORS-ENABLE, we need to install the CORS module by type this command.

npm install cors --save

Add this line to enable the CORS in Node, Express.js application.

app.use(cors());

Initialize passport by add this line.

app.use(passport.initialize());

Replace default landing page and route with these lines.

app.get('/', function(req, res) {
  res.send('Page under construction.');
});

app.use('/api', api);

 


Create Express Models

We need to create a User model for authentication and authorized data. Create models folder first on the root of the project.

mkdir models

Create these Javascript files for models.

touch models/book.js
touch models/user.js

Open and edit "models/book.js" then add these lines of codes.

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var BookSchema = new Schema({
  isbn: {
    type: String,
    required: true
  },
  title: {
    type: String,
    required: true
  },
  author: {
    type: String,
    required: true
  },
  publisher: {
    type: String,
    required: true
  }
});

module.exports = mongoose.model('Book', BookSchema);

Open and edit "models/user.js" then add these lines of codes that implementing secure log in or authentication schema which save the password as an encrypted value using Bcrypt.

var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var bcrypt = require('bcrypt-nodejs');

var UserSchema = new Schema({
  username: {
        type: String,
        unique: true,
        required: true
    },
  password: {
        type: String,
        required: true
    }
});

UserSchema.pre('save', function (next) {
    var user = this;
    if (this.isModified('password') || this.isNew) {
        bcrypt.genSalt(10, function (err, salt) {
            if (err) {
                return next(err);
            }
            bcrypt.hash(user.password, salt, null, function (err, hash) {
                if (err) {
                    return next(err);
                }
                user.password = hash;
                next();
            });
        });
    } else {
        return next();
    }
});

UserSchema.methods.comparePassword = function (passw, cb) {
    bcrypt.compare(passw, this.password, function (err, isMatch) {
        if (err) {
            return cb(err);
        }
        cb(null, isMatch);
    });
};

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

The different User models are an additional function for creating an encrypted password using "Bcrypt" and function for the compared encrypted password.


Create Express Routers for REST API

Now, it's time for the real game. We will create a Router for authenticating the user and restrict resources. In routes, folder creates a new Javascript file.

touch routes/api.js

Open and edit "routes/api.js" then declares all require variables.

var mongoose = require('mongoose');
var passport = require('passport');
var config = require('../config/database');
require('../config/passport')(passport);
var express = require('express');
var jwt = require('jsonwebtoken');
var router = express.Router();
var User = require("../models/user");
var Book = require("../models/book");

Create a router for signup or register the new user.

router.post('/signup', function(req, res) {
  if (!req.body.username || !req.body.password) {
    res.json({success: false, msg: 'Please pass username and password.'});
  } else {
    var newUser = new User({
      username: req.body.username,
      password: req.body.password
    });
    // save the user
    newUser.save(function(err) {
      if (err) {
        return res.json({success: false, msg: 'Username already exists.'});
      }
      res.json({success: true, msg: 'Successful created new user.'});
    });
  }
});

Create a router for login or sign-in.

router.post('/signin', function(req, res) {
  User.findOne({
    username: req.body.username
  }, function(err, user) {
    if (err) throw err;

    if (!user) {
      res.status(401).send({success: false, msg: 'Authentication failed. User not found.'});
    } else {
      // check if password matches
      user.comparePassword(req.body.password, function (err, isMatch) {
        if (isMatch && !err) {
          // if user is found and password is right create a token
          var token = jwt.sign(user, config.secret);
          // return the information including token as JSON
          res.json({success: true, token: 'JWT ' + token});
        } else {
          res.status(401).send({success: false, msg: 'Authentication failed. Wrong password.'});
        }
      });
    }
  });
});

Create a router to add a new book that only accessible to an authorized user.

router.post('/book', passport.authenticate('jwt', { session: false}), function(req, res) {
  var token = getToken(req.headers);
  if (token) {
    console.log(req.body);
    var newBook = new Book({
      isbn: req.body.isbn,
      title: req.body.title,
      author: req.body.author,
      publisher: req.body.publisher
    });

    newBook.save(function(err) {
      if (err) {
        return res.json({success: false, msg: 'Save book failed.'});
      }
      res.json({success: true, msg: 'Successful created new book.'});
    });
  } else {
    return res.status(403).send({success: false, msg: 'Unauthorized.'});
  }
});

Create a router for getting a list of books that accessible for an authorized user.

router.get('/book', passport.authenticate('jwt', { session: false}), function(req, res) {
  var token = getToken(req.headers);
  if (token) {
    Book.find(function (err, books) {
      if (err) return next(err);
      res.json(books);
    });
  } else {
    return res.status(403).send({success: false, msg: 'Unauthorized.'});
  }
});

Create a function for parse authorization token from request headers.

getToken = function (headers) {
  if (headers && headers.authorization) {
    var parted = headers.authorization.split(' ');
    if (parted.length === 2) {
      return parted[1];
    } else {
      return null;
    }
  } else {
    return null;
  }
};

Finally, export router as a module.

module.exports = router;


Run and Test Secure Node.js, Express.js, MongoDB REST API

Now, it's time to run and test this secure REST API. Type this command to run the server.

nodemon

You will see this log in the terminal if the server runs correctly.

[nodemon] 1.11.0
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node ./bin/www`

We will test our secure REST API using the Postman REST Client. You can install Postman for Chrome extension.

Now, open Postman then enters method, address (http://localhost:3000/api/signup) and body parameters for create or signup new user.

Node, Express, Mongoose and Passport.js REST API Authentication - Postman Signup

After click Send button and successfully created a new user, you should see this message.

Node, Express, Mongoose and Passport.js REST API Authentication - Signup success

Next, we have to test if REST API for Book resource is restricted for the authorized user only. Change method to "GET" and API endpoint to "http://localhost:3000/api/book" then click Send button. You should see this message on the Postman result.

Unauthorized

To access the book resource, we have to log in using the previously registered user. Change method to "POST" and endpoint to "http://localhost:3000/api/signin" then fill credentials like below screenshot.

Node, Express, Mongoose and Passport.js REST API Authentication - Postman Signin

If login is successful, we should get a JWT token like below.

{
  "success": true,
  "token": "JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyIkX18iOnsic3RyaWN0TW9kZSI6dHJ1ZSwic2VsZWN0ZWQiOnt9LCJnZXR0ZXJzIjp7fSwid2FzUG9wdWxhdGVkIjpmYWxzZSwiYWN0aXZlUGF0aHMiOnsicGF0aHMiOnsicGFzc3dvcmQiOiJpbml0IiwidXNlcm5hbWUiOiJpbml0IiwiX192IjoiaW5pdCIsIl9pZCI6ImluaXQifSwic3RhdGVzIjp7Imlnbm9yZSI6e30sImRlZmF1bHQiOnt9LCJpbml0Ijp7Il9fdiI6dHJ1ZSwicGFzc3dvcmQiOnRydWUsInVzZXJuYW1lIjp0cnVlLCJfaWQiOnRydWV9LCJtb2RpZnkiOnt9LCJyZXF1aXJlIjp7fX0sInN0YXRlTmFtZXMiOlsicmVxdWlyZSIsIm1vZGlmeSIsImluaXQiLCJkZWZhdWx0IiwiaWdub3JlIl19LCJlbWl0dGVyIjp7ImRvbWFpbiI6bnVsbCwiX2V2ZW50cyI6e30sIl9ldmVudHNDb3VudCI6MCwiX21heExpc3RlbmVycyI6MH19LCJpc05ldyI6ZmFsc2UsIl9kb2MiOnsiX192IjowLCJwYXNzd29yZCI6IiQyYSQxMCRCLjByc3lnTHEwMzE4Njk5RWNlTU9lMllqWlJQZ3ZwL1VhZk8yb25OUkwuZDVWR3hmUjlOZSIsInVzZXJuYW1lIjoidGVzdEBleGFtcGxlLmNvbSIsIl9pZCI6IjU4ZWI5MzljNGE4MGYzNGU4OGU2NGY2MiJ9LCJpYXQiOjE0OTE4MzQ0OTF9.O2ljjVJVYBt65b0bTWnjyU-IDwJ9gXfDbzqDO7lccWc"
}

Just copy and paste the token value for use in request headers of restricted book resource. Now, do previous get book and add this header.

Node, Express, Mongoose and Passport.js REST API Authentication - Authorized Request

If you see the blank array in response, then you are authorized to use book resources. Now, you can do the same thing for posting new book.

That it's for now, sorry if this tutorial not perfect and too many incomplete words because we are writing this tutorial quicker. Any suggestion, critics or trouble report are welcome in the comments section at the bottom of this page.

The full source code is on our Github

For more detailed on MEAN stack and Node.js, you can take the following course:

Thanks