Although this question has been asked numerous times before, I have not been able to find a satisfactory answer on StackOverflow.
I am new to learning express
and am attempting to create an application with both backend and frontend using JavaScript libraries after transitioning from the PHP
world. Everything seems to be functioning correctly except for the fact that the comparePassword
method does not return a matched password.
I am utilizing the bcryptjs
library for password hashing and comparing, as well as the 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);
})
});
}
));