Labels

Java (10) Spring (10) Spring MVC (6) Web Services (5) Rest (4) Javascript (3) Nodejs (3) Spring Batch (3) Angular (2) Angular2 (2) Angular6 (2) Expressjs (2) Passportjs (2) SOAP (2) SOAPUI (2) Spring Boot (2) AJAX (1) H2 (1) JQuery (1) JUnit (1) Npm (1) Puppeteer (1) Python (1) RaspberryPi (1) Raspbian (1) SQL (1) SQLite (1) Scripts (1) html (1)

Tuesday, November 13, 2018

JWT authentification with passportjs and angular 6 - Part 1 backend

Passportjs is a great nodejs library that allows us to work with different types of authentification in a very simple way. In this tutorial we will build a simple blog-like application with a nodejs backend and an angular 6 frontend.

In our blog we will login, logout, register users. Also we will have blog entries that we will add and query. You can download the code from this github repo

PART1. Nodejs Backend

Generation of the project

We will use the node library expressjs to build a simple rest service, which we will later make secure with passportjs.

We first install the express app generator in case we do not have it

npm install express-generator -g

then we use the following command to create a new expressjs project:

express passportjs-node-backend
We will see the that it has created this file structure:



Database configuration and Passportjs configuration

Now we will need to add the database configuration. We will use Mongodb for our persistence, passportjs will automatically create all the user and password collection when the time comes.

Let us create a directory config and two files database.js and passport.js


Here we configure the mongo url and the secret name:

database.js

module.exports = {
  'secret': 'meansecure',
  'database': 'mongodb://localhost:27017/test'
};

Here we configure passporjs to use jwt token


passport.js


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.fromAuthHeaderWithScheme("jwt");
  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);
          }
      });
  }));
};

To access the mongodb database, we will use the library Mongoose. We need to add two models, one for the users and one for the blog entries. Let us create a models folder and two files: BlogEntry.js and User.js:

Let us start with the model User. This is the one that passport will use to create and query users. We will add a function "comparePassword" that will manage the encription and comparation of passwords for us.

User.js

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

var UserSchema = new Schema({
    username: {
        type: String,
        unique: true,
        required: true
    },
    email: {
        type: String,
    },
    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);

Have in mind that we are using the libraries bcrypt-nodejs and mongoose, which we will have to install with npm.

For our blog we will also need entries. So let us define the database model for that, we are adding the basic fields for a blog post: title, content and tags

BlogEntry.js


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

var BlogEntrySchema = new Schema({
    title: {
        type: String,
        required: true
    },
    content: {
        type: String,
    },
    tags: {
        //type: Array[String],
        type: String,
        required: true
    }
},
    { collection: 'entries' });

module.exports = mongoose.model('BlogEntry', BlogEntrySchema);

Routes


Now we can create the new routes. Let us create the files entries.js and security.js in the routes dir



 We will create first routes for login and signup inside the file security.js. We do not need a logout route because that will be done from the frontend just by deleting the token from the local storage


security.js

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 BlogEntry = require("../models/BlogEntry");

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,
      email: req.body.email,
      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.' });
      console.log('Successful created new user.' + newUser.username)
    });
  }
});


router.post('/login', 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.toJSON(), 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.' });
        }
      });
    }
  });
});


module.exports = router;


We will also need  routes for the entries. We will have three routes, one for saving new posts one for retrieving the list of posts and one for retrieving by title:

BlogEntries.js

var express = require('express');
var router = express.Router();
var BlogEntry = require("../models/BlogEntry");
var passport = require('passport');

router.post('/save_entry', passport.authenticate('jwt', { session: false }), function (req, res) {
  var token = getToken(req.headers);
  if (token) {
    console.log(req.body);
    var newPost = new BlogEntry({
      title: req.body.title,
      content: req.body.content,
      tags: req.body.tags
    });

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

router.get('/titled/:title', function (req, res) {
  BlogEntry.findOne({ title: req.params.title }).exec(function (err, entry) {
    if (err) throw err;
    return res.json(entry);
  });
});

router.get('/entries_list/limit=:limit&skip=:skip', function (req, res) {
  let limit;
  let skip;
  if (req.params.limit) {
    limit = parseInt(req.params.limit);
  }
  if (req.params.skip) {
    skip = parseInt(req.params.skip)
  }
  BlogEntry.find({}).limit(limit).skip(skip).exec(function (err, entry) {
    if (err) throw err;
    return res.json(entry);
  });
});

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;
  }
};

module.exports = router;

Notice that we are retrieving the data using pagination via "limit" and "skip" parameters. To search we just invoke the Mongoose model and use the mongodb function .find like this

...
BlogEntry.find({}).limit(limit).skip(skip).exec(function (err, entry) {
    if (err) throw err;
    return res.json(entry);
  });
...

Configure all in app.js


The file app.js is always the most important file in an express project, here we import all the configuration (routes, passportjs, database...).

app.js

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var cors = require('cors');

var indexRouter = require('./routes/index');
var securityRouter = require('./routes/security');
var entriesRouter = require('./routes/entries');

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

mongoose.Promise = require('bluebird');
mongoose.connect(config.database, { promiseLibrary: require('bluebird') })
  .then(() => console.log('connection succesful'))
  .catch((err) => console.error(err));


var app = express();


// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

app.use(bodyParser.json({ limit: '50mb' }));
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));

app.use(passport.initialize());
app.use(cors());


app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));


app.use('/', indexRouter);
app.use('/security', securityRouter);
app.use('/entries', entriesRouter);

// catch 404 and forward to error handler
app.use(function (req, res, next) {
  next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

sanity test

check that everything works using 
npm start

go to localhost:3000, you will see:


DOWNLOAD THE CODE:

CONTINUE:

We continue with the tutorial in PART2, were we will build the front end with angular.

3 comments: