1

Firstly, I know that the very same question has been asked many times before me but I couldn't find an answer in any of those questions I found on StackOverflow.

I just recently started learning express and for the first time, I'm trying to create application, both, backend and frontend with javascript libraries (coming out from PHP world). I've declared a MongoDB model schema with some pre-work and a function that compares the inputted password to a hashed password stored in the database. Everything else seems to work just fine except that the comparePassword method never returns a matched password.

I am using an bcryptjs library for password hashing and comparing and a passport library for authentication.

User model (models/user.js):

var mongoose            = require('mongoose'),
    Schema              = mongoose.Schema,
    bcrypt              = require('bcryptjs');
    SALT_WORK_FACTOR    = 10;

var userSchema = new Schema({
    //id: ObjectId,
    email: {
        type: String,
        unique: true,
        required: true
    },
    name: {
        type: String,
        required: true
    },
    password: {
        type: String,
        required: true
    }
});

userSchema.pre('save', function(next) { // Hash the password before adding it to the database
    var user = this;

    // only hash the password if it has been modified (or is new)
    if (!user.isModified('password')) return next();

    // generate a salt
    bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
        if (err) return next(err);

        // hash the password using our new salt
        bcrypt.hash(user.password, salt, function(err, hash) {
            if (err) return next(err);

            // override the cleartext password with the hashed one
            user.password = hash;
            next();
        });
    });
});

userSchema.methods.comparePassword = function(candidatePassword, cb) {
    var user = this;
    bcrypt.compare(candidatePassword, user.password, function(err, isMatch) {
        console.log(candidatePassword);
        console.log(user.password);
        console.log((candidatePassword === user.password) ? 'passwords match' : 'passwords dont match' );
        return;
        if (err) return cb(null, err);
            cb(null, isMatch);
    });
};
module.exports = mongoose.model('User', userSchema);

Authentication strategy (config/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); }

            if (!user) { // Return if user not found in database
                return done(null, false, {
                    message: 'User not found'
                });
            }

        // It will always output "Incorrect creditentials"
            if (!user.comparePassword(password)) { 
                return done(null, false, {
                    error: true,
                    message: 'Incorrect creditentials'
                });
            }
            return done(null, user); // If credentials are correct, return the user object
        });
    }
));

And finally, my route for signing in (routes/auth.js):

var router = require('express').Router(); // get router instance
var request = require('request');
var passport = require('passport');
var User = require('../../models/user');
var tokenAuth = require('../../middlewares/token');

router.post('/signin', function(req, res) {
    passport.authenticate('local', function(err, user, info){
        var token;

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

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

});

module.exports = router;

EDIT:

If I remove the console.log output in:

bcrypt.compare(candidatePassword, user.password, function(err, isMatch) {
            console.log(candidatePassword);
            console.log(user.password);
            console.log((candidatePassword === user.password) ? 'passwords match' : 'passwords dont match' );
            return;
            if (err) return cb(null, err);
                cb(null, isMatch);
        });
    };

and try to execute the callback function, I will get the following error:

cb(null, isMatch);
        ^
TypeError: undefined is not a function
    at D:\project\backend\dist\models\user.js:51:9
    at D:\project\node_modules\bcryptjs\dist\bcrypt.js:297:21
    at D:\project\node_modules\bcryptjs\dist\bcrypt.js:1250:21
    at Object.next [as _onImmediate] (D:\project\node_modules\bcryptjs\dist\bcrypt.js:1130:21)
    at processImmediate [as _immediateCallback] (timers.js:354:15)

EDIT 2:

So, I finally I was able to compare the passwords and was able to console.log whether the passwords match or not. I was able to pull this off with Promises. Now I'm unsure how to pass that Promise to the passport handler so that it can return the user results for the routes.

Here's the comparePassword method:

userSchema.methods.comparePassword = function(candidatePassword) {
    var user = this;

    return new Promise(function(resolve,reject)
    {
        bcrypt.compare(candidatePassword, user.password, function (err, isMatch) {
            // Prevent conflict btween err and isMatch
            if (err)
                reject(new Error("Error checking use password"));
            else
                console.log(isMatch === true ? 'passwords match' : 'passwords dont match');
                return;
                resolve(isMatch);
        });
    });
};

and the passport.js:

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
            user.comparePassword(password).then(function(isMatch) {
                return isMatch === true ? user : null; // How to pass the user object to route??
            }).catch(function (err) { // handle possible errors
                return done(err);
            })
        });
    }
));
B_CooperA
  • 659
  • 1
  • 9
  • 28

1 Answers1

1

I thought you just passing callback in bcrypt compare. Make sure you pass plaintextpassword as parameter and comparing it with hash password from db.

Instead of doing this

 if (!user.comparePassword(password)) { 
     return done(null, false, {
         error: true,
                message: 'Incorrect creditentials'
     });
 }

Why don't do it like this

user.comparePassword(function (err, match) {
     if (err) throw err;
     if (!match) {
         return done(null, false, {
              error: true,
              message: 'Incorrect creditentials'
         });
     } else {
         // Password match
     }
});

and in bcrypt compare method, change the callback param, err must be the first and res must be the second

userSchema.methods.comparePassword = function(candidatePassword, cb) {
    var user = this;
    bcrypt.compare(candidatePassword, user.password, function(err, isMatch) {
        console.log(candidatePassword);
        console.log(user.password);
        // You shouldn't compare the password directly like this. Let the method handle it and once the response is return (isMatch), pass it as callback param. Comment this line, you don't need it
        //console.log((candidatePassword === user.password) ? 'passwords match' : 'passwords dont match' );
        //return;
        // Prevent conflict btween err and isMatch
        if (err) return cb(err, null);
            cb(null, isMatch);
    });
};

EDIT

You need to call done when password is match and pass user object

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
            user.comparePassword(password).then(function(isMatch) {
                if (isMatch) {
                    return done(null, user);
                } else {
                    return done(null, false);
                }
            }).catch(function (err) { // handle possible errors
                return done(err);
            })
        });
    }
));

In Route Middeware

I guess your route is something like this

app.post('/login',
  passport.authenticate('local', {
    successRedirect: '/loginSuccess',
    failureRedirect: '/loginFailure'
  })
);

app.get('/loginFailure', function(req, res, next) {
    res.send('Failed to authenticate');
});

// Login success should return user object
app.get('/loginSuccess', function(req, res, next) {
  res.send('Successfully authenticated');
});
digit
  • 4,479
  • 3
  • 24
  • 43
  • i tried this and the behavior is still the same as with my original code: if i try to console the match, it will output `"passwords don't match"` and if I remove the `return` and try the callback I get an error: `cb(null, isMatch); TypeError: undefined is not a function` – B_CooperA Jan 15 '17 at 08:45
  • Oh. I just notice that you put return on the line after console.log. What the point? Do you want to prevent the error. You cannot compare it directly by using equivalentString like that, it will not work. Let bcrypt.compare handle it and try console.log(isMatch), what you will get. – digit Jan 15 '17 at 09:01
  • That error means that cb is not recognised as a function. You need to pass callback function as i stated in the above answer. – digit Jan 15 '17 at 09:03
  • Would you please enlighten me a bit more? I'm relatively new with Javascript since I have been coding mostly with PHP and similiar server-side languages and am a bit confused about those callbacks, promises etc. – B_CooperA Jan 15 '17 at 09:43
  • Ohh i see. Callback and promise are created to handle asncyronous actually. So, nodejs is an asynchronously nature , then you cannot run away from using both of these 2. You pick which one to use whether callback or promise. There are a lot of simple documentation that explain the details about callback and promise. – digit Jan 15 '17 at 09:56
  • Yeah, I'm more familiar with the `Promise` concept and actually was able to handle the password comparison through Promises and now it does return whether passwords match or not. I just don't know how to handle that `Promise` properly in the `passport.js` Strategy so that it can be used in my routes. Could you please be so kind and give me some tips? I've been struggling with this for too long already. – B_CooperA Jan 15 '17 at 10:12
  • Well, i'm not very familiar with passportjs. I just ran out some other tutorials.. That all i can help you. Cheers :) – digit Jan 15 '17 at 11:43
  • I think you can refer this stackoverflow which discuss about custom passport authenticate callback. http://stackoverflow.com/questions/15711127/express-passport-node-js-error-handling. – digit Jan 15 '17 at 11:52