The comparison of bcrypt passwords using the compare method consistently leads to errors

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

Answer №1

When using bcrypt to compare passwords, make sure you pass the plaintext password as a parameter and compare it with the hashed password retrieved from the database.

Instead of the approach shown below:

 if (!user.comparePassword(password)) { 
     return done(null, false, {
         error: true,
                message: 'Incorrect credentials'
     });
 }

Consider implementing it this way:

user.comparePassword(function (err, match) {
     if (err) throw err;
     if (!match) {
         return done(null, false, {
              error: true,
              message: 'Incorrect credentials'
         });
     } else {
         // Passwords match
     }
});

In the bcrypt comparison method, ensure that the callback parameters are in the correct order - 'err' should come first and 'res' second:

userSchema.methods.comparePassword = function(candidatePassword, cb) {
    var user = this;
    bcrypt.compare(candidatePassword, user.password, function(err, isMatch) {
        // Avoid direct password comparison here. Let the method handle it and receive the result (isMatch) as the callback parameter.
        if (err) return cb(err, null);
            cb(null, isMatch);
    });
};

EDIT

Remember to call 'done' when the password matches and pass the user object:

passport.use(new LocalStrategy({
        usernameField: 'email'
    },
    function(username, password, done) {
        User.findOne({ email: username }, function (err, user) {
            if (err) { return done(err); }
            user.comparePassword(password).then(function(isMatch) {
                if (isMatch) {
                    return done(null, user);
                } else {
                    return done(null, false);
                }
            }).catch(function (err) {
                return done(err);
            })
        });
    }
));

In Route Middleware

If your routes look like this:

app.post('/login',
  passport.authenticate('local', {
    successRedirect: '/loginSuccess',
    failureRedirect: '/loginFailure'
  })
);

app.get('/loginFailure', function(req, res, next) {
    res.send('Failed to authenticate');
});

// Successful login should return the user object
app.get('/loginSuccess', function(req, res, next) {
  res.send('Successfully authenticated');
});

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What are the solutions to repair a triple drop-down menu?

I am struggling with the code below which contains numbers inside the values: What I need is to modify all instances of the following codes: <option value="1" class="sub_1"> To look like this: <option value="" id="1" class="sub_1"> Basical ...

Hide the div element when the url contains the word 'word'

I did some research online, but I couldn't find any information on whether this is doable. I'm currently using IPBoard and after their latest IPS4 update, I'm facing an issue where I can't simply change the homepage anymore. Now, I have ...

What are some methods to turn off AJAX caching in JavaScript without relying on jQuery?

I'm running into an issue where my current method of framing the ajax url seems to be caching the old response on the second call. How can I ensure that I always get the latest response? Let me know if you need more information. Snippet of Code : va ...

Developing a division with an image source based on the selection of a checkbox

My goal is to develop a "change function" that generates a new div for each checked checkbox selection and removes the div when the checkbox is unchecked. These new divs should also display the img src of the selected checkbox. Currently, my JavaScript c ...

Issue with the placement of the bottom background when viewed in responsive mode

I'm encountering an issue with the placement of a bottom white image when I switch to responsive mode. It seems to not align at the bottom of the parent ul element as expected. If possible, could you provide assistance in addressing this issue? The i ...

ExpressJS - The execution of JavaScript in this framework does not strictly follow a top-down approach

Having issues with this particular script not running as expected, particularly in the variable reassignment section. Below is the code snippet and output: // Code to Create New Open Market in the game let latestRowId = 1; var sqlQu ...

Is there a way to execute a docker image, developed in a linux environment, on a windows system?

After obtaining a docker image of a basic expressjs server and setting up port 3000 on a docker desktop in Windows, I encountered an issue in the integrated terminal. Attempting to run any docker-related command resulted in an error stating that docker was ...

An ongoing problem in ThreeJS involves the resizing of images that are not in a power of 2

My issue involves the automatic resizing of textures by WebGLRenderer in threejs. I understand that WebGL requires textures to have dimensions that are powers of 2. In my case, the texture has a wrap set as RepeatWrapping and a size of 65536 x 512, which ...

Using Firebase to loop through elements is made possible with ng

I'm currently faced with the challenge of using an ng-repeat to iterate through some JSON data that I have imported into Firebase. Below is the snippet of HTML code that I am working with: <div class="col-md-4" ng-repeat="place in places"> &l ...

Challenge with validating custom start and end dates in Angular Material datepicker

I am dealing with two dates, namely an arrival date and an exit date. I am attempting to implement custom validation where the exit date cannot be earlier than the arrival date. If it is earlier, then display an error message. Please refer to the code snip ...

"Is there a way to extract a specific value from an XML file based on another element

<dataset> <study recNO="0" seriesRecCount="1"> <patientID>123456</patientID> <accessionNumber>M120170428105320</accessionNumber> <patientIDID>35</patientIDID> <studyUID>2.25.1439 ...

Preflight request rejection due to access control check failure in React and Express

I am facing a similar issue to this question, but my problem is specific to using Express with React on the frontend. The error I am encountering is: https://i.sstatic.net/1OIfy.png Access to fetch at 'http://localhost:8000/api/users/auth/github& ...

Using various NextJS layouts or importing CSS conditionally based on the requirements

I'm currently in the process of transitioning a legacy app to Nextjs. This app consists of two distinct layouts: one for the public facing aspect and another for the admin section. Each layout has its own set of CSS files. However, due to Nextjs limit ...

Submitting the form may cause disruptions for others

I currently have an email subscription form for my newsletter that is managed through PHP. This form appears in the footer of every page on my website. Check out a demonstration on JSFIDDLE While the form itself functions properly, I am encountering issu ...

What separates a typical update from using the $set operator for updating?

Standard procedure: module.exports = (_id, newInfo) => { return User.update( {_id} , newInfo); }; Alternative approach with $set operator: module.exports = (_id, newInfo) => { return User.update( {_id} , {$set: newInfo} ); }; ...

Guide to implementing a click-triggered notification using JavaScript

Can anyone assist me with a JavaScript issue regarding applying toast notifications? I need guidance on implementing an onClick toast using JavaScript. The toast should only appear when a specific condition is false; if the condition is true, the page sho ...

Are cookie-session and JWT tokens essentially the same thing in Express, or is there a difference between them?

When looking at the express documentation, you will come across a differentiation between express-session and cookie-session. The explanation provided is as follows: There are two main ways to store a user session with cookies: either on the server or on ...

Develop a customized modal pop-up for every object using React

In my React app, I have a list of cards with buttons on each one. When the button is clicked, it should open a modal popup displaying some information. However, I am facing an issue where I can't create a unique modal for each card because the data t ...

Encountering a 500 (Internal Server Error) when attempting to post form-data using React JS on a Node server

POST https://waste-alert-api.herokuapp.com/datas 500 (Internal Server Error) In my latest project, I have developed a React application where users can upload an image along with some data. While most of the methods are functioning correctly, I am experie ...

Trigger an event once a script is called in an external HTML file loaded through Ajax

My webpage features an Ajax Loaded Div, within which a Jquery plugin loads my Google Spreadsheet. I am struggling to add event listeners to detect when the spreadsheet is fully loaded due to the unpredictable loading time and the limitations of listening f ...