Bcrypt-NodeJS compare() returns false whatever the password Bcrypt-NodeJS compare() returns false whatever the password mongoose mongoose

Bcrypt-NodeJS compare() returns false whatever the password


I know the solution has been found but just in case you are landing here out of google search and have the same issue, especially if you are using a schema.pre("save") function, sometimes there is a tendency of saving the same model several times, hence re-hashing the password each time. This is especially true if you are using references in mongoDB to create schema relationship. Here is what my registration function looked like:

Signup Code

User.create(newUser, (err, user) => {            if (err || !user) {                console.warn("Error at stage 1");                return res.json(transformedApiRes(err, "Signup error", false)).status(400);            }            let personData: PersonInterface = <PersonInterface>{};            personData.firstName = req.body.first_name;            personData.lastName = req.body.last_name;            personData.user = user._id;            Person.create(personData, function (err1: Error, person: any): any {                if (err1 || !person) {                    return res.json(transformedApiRes(err1, "Error while saving to Persons", false));                }                /* One-to-One relationship */                user.person = person;                user.save(function (err, user) {                    if (err || !user) {                        return res.json({error: err}, "Error while linking user and person models", false);                    }                    emitter.emit("userRegistered", user);                    return res.json(transformedApiRes(user, `Signup Successful`, true));                });            });        });

As you can see there is a nested save on User because I had to link the User model with Person model (one-to-one). As a result, I had the mismatch error because I was using a pre-save function and every time I triggered User.create or User.save, the function would be called and it would re-hash the existing password. A console statement inside pre-save gave me the following, showing that indeed that password was re-hashed:

Console debug after a single signup call

{ plain: 'passwd',  hash: '$2b$10$S2g9jIcmjGxE0aT1ASd6lujHqT87kijqXTss1XtUHJCIkAlk0Vi0S' }{ plain: '$2b$10$S2g9jIcmjGxE0aT1ASd6lujHqT87kijqXTss1XtUHJCIkAlk0Vi0S',  hash: '$2b$10$KRkVY3M8a8KX9FcZRX.l8.oTSupI/Fg0xij9lezgOxN8Lld7RCHXm' }

The Fix, The Solution

To fix this, you have to modify your pre("save") code to ensure the password is only hashed if it is the first time it is being saved to the db or if it has been modified. To do this, surround your pre-save code in these blocks:

if (user.isModified("password") || user.isNew) {    //Perform password hashing here} else {    return next();}

Here is how the whole of my pre-save function looks like

UsersSchema.pre("save", function (next: NextFunction): any {    let user: any = this;    if (user.isModified("password") || user.isNew) {        bcrypt.genSalt(10, function (err: Error, salt: string): any {            if (err) {                return next(err);            }            bcrypt.hash(user.password, salt, function (err: Error, hash: string) {                if (err) {                    console.log(err);                    return next(err);                }                console.warn({plain: user.password, hash: hash});                user.password = hash;                next();            });        });    } else {        return next();    }});

Hopefully this helps someone.


I am dropping this here because it might help someone someday.

In my own case, the reason why I was having bcrypt.compare as false even when I supplied the right authentication details was because of the constraints on the datatype in the model. So each time the hash was saved in the DB, it was truncated in order to fit into the 50 characters constraints.

I had

    'password': {      type: DataTypes.STRING(50),      allowNull: false,      comment: "null"    },

The string could only contain 50 characters but the result of bcrypt.hash was more than that.

FIX

I modified the model thus DataTypes.STRING(255)


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.