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