Search code examples
node.jsmongoosebcrypt

Bcrypt-NodeJS compare() returns false whatever the password


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?

  1. bcrypt.compare() always returns false whatever the password is correct or not, and whatever the password (I tried several strings).
  2. The password is only hashed once (so the hash is not re-hashed) on user's creation.
  3. The password and the hash given to the compare method are the right ones, in the right order.
  4. The password and the hash are of type "String".
  5. The hash isn't truncated when stored in the database (60 characters long string).
  6. 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.


Solution

  • bcrypt.hash() has 3 arguments... you have 4 for some reason.

    Instead of

    bcrypt.hash(password, bcrypt.genSaltSync(12), null, function(err, hash) {
    

    it should be

    bcrypt.hash(password, bcrypt.genSaltSync(12), function(err, hash) {
    

    Since you were hashing only during user creation, then you might not have been hashing properly. You may need to re-create the users.