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

How to securing or authenticating Node, Express and Mongoose REST API using Passport.js (Latest versions).

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

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.

 


1. 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 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 required libraries and dependencies. Type this commands to install library and dependencies.

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


2. Configure Node.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 this lines of codes.

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

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

Open and edit "config/passport.js" then add this 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 use for getting user by matching JWT token with token get from the client. This configuration needs to create 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 this 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);

Declare a variable for API route.

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

To make this server CORS-ENABLE add this lines of codes.

app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});

Initialize passport by add this line.

app.use(passport.initialize());

Replace default landing page and route with this lines.

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

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

 


3. Create Models

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

mkdir models

Create this Javascript files for models.

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

Open and edit "models/book.js" then add this 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 this lines of codes.

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 of User models are an additional function for creating an encrypted password using "Bcrypt" and function for compared encrypted password.


4. Create Routers for REST API

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

touch routes/api.js

Open and edit "routes/api.js" then declares all requires 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 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 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 router for add new book that only accessible to 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 router for getting list of books that accessible for 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 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;


5. Run and Test Secure 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 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 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 a 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

Thanks

All Partners-10usd 336x280
Loading…