I know that question has already been asked a few times (like here, here or there, or even on Github, but none of the answers actually worked for me...
I am trying to develop authentication for a NodeJS app using Mongoose and Passport, and using Bcrypt-NodeJS to hash the users' passwords.
Everything was working without any problem before I decided to refactor the User schema and to use the async methods of bcrypt. The hashing still works while creating a new user but I am now unable to verify a password against its hash stored in MongoDB.
What do I know?
bcrypt.compare()
always returnsfalse
whatever the password is correct or not, and whatever the password (I tried several strings).- The password is only hashed once (so the hash is not re-hashed) on user's creation.
- The password and the hash given to the compare method are the right ones, in the right order.
- The password and the hash are of type "String".
- The hash isn't truncated when stored in the database (60 characters long string).
- The hash fetched in the database is the same as the one stored on user's creation.
Code
User schema
Some fields have been stripped to keep it clear, but I kept the relevant parts.
var userSchema = mongoose.Schema({
// Local authentication
password: {
hash: {
type: String,
select: false
},
modified: {
type: Date,
default: Date.now
}
},
// User data
profile: {
email: {
type: String,
required: true,
unique: true
}
},
// Dates
lastSignedIn: {
type: Date,
default: Date.now
}
});
Password hashing
userSchema.statics.hashPassword = function(password, callback) {
bcrypt.hash(password, bcrypt.genSaltSync(12), null, function(err, hash) {
if (err) return callback(err);
callback(null, hash);
});
}
Password comparison
userSchema.methods.comparePassword = function(password, callback) {
// Here, `password` is the string entered in the login form
// and `this.password.hash` is the hash stored in the database
// No problem so far
bcrypt.compare(password, this.password.hash, function(err, match) {
// Here, `err == null` and `match == false` whatever the password
if (err) return callback(err);
callback(null, match);
});
}
User authentication
userSchema.statics.authenticate = function(email, password, callback) {
this.findOne({ 'profile.email': email })
.select('+password.hash')
.exec(function(err, user) {
if (err) return callback(err);
if (!user) return callback(null, false);
user.comparePassword(password, function(err, match) {
// Here, `err == null` and `match == false`
if (err) return callback(err);
if (!match) return callback(null, false);
// Update the user
user.lastSignedIn = Date.now();
user.save(function(err) {
if (err) return callback(err);
user.password.hash = undefined;
callback(null, user);
});
});
});
}
It may be a "simple" mistake I made but I wasn't able to find anything wrong in a few hours... May you have any idea to make that method work, I would be glad to read it.
Thank you guys.
Edit:
When running this bit of code, match is actually equal to true
. So I know my methods are correct. I suspect this has something to do with the storage of the hash in the database, but I really have no idea of what can cause this error to occur.
var pwd = 'TestingPwd01!';
mongoose.model('User').hashPassword(pwd, function(err, hash) {
console.log('Password: ' + pwd);
console.log('Hash: ' + hash);
user.password.hash = hash;
user.comparePassword(pwd, function(err, match) {
console.log('Match: ' + match);
});
});
Edit 2 (and solution) :
I put it there in case it could be helpful to someone one day...
I found the error in my code, which was occurring during the user's registration (and actually the only piece of code I didn't post here). I was hashing the user.password
object instead of user.password.plaintext
...
It's only by changing my dependencies from "brcypt-nodejs" to "bcryptjs" that I was able to find the error because bcryptjs throws an error when asked to hash an object, while brcypt-nodejs just hashes the object as if it were a string.