Authentication is returning "401 (Unauthorized)" when it shouldn't

I am setting up an authentication functionality for my first time and am getting some unexpected results after a user has been logged in. A colleague has given me an application with working authentication to model my application after and it seems like everything I have done is correct.

I am using AngularJS on the front-end, SailsJS back-end framework, and PassportJS authentication middleware.

My source is (for now) stored publicly… the backend API is on Github here (API Github) and the front-end code is found here (Front-End Github)

What basically happens is that the user

  1. Hits the login button, which triggers this function

    login: function (credentials) {
        return baseAuth.customPOST(credentials, 'login').then(function (user) {
            $log.debug('User logged in: ', user);
            isAuthenticated = true;
            return user;
        });
    },
    
  2. The controller logic for that customPOST is this

    login: function (req, res, next) {
       passport.authenticate('local', function (err, user, info) {
           if (err) { return res.json(err); }
           else if (user) {
               req.logIn(user, function (err) {
                   if (err) { return res.json(err); }
                   return res.json(user);
               });
           } else { return res.json(400, info); }
    
       })(req, res);
    
    },
    
  3. In that step, passport should do its thing and login the user

    passport.use(new LocalStrategy({ usernameField: 'email', passwordField: 'password' },
    function(email, password, next) {
       User.findOne({ email: email })
           .exec(function (err, user) {
               if (err) { return next(err); }
               if (user) {
                    if (user.activated) {
                    bcrypt.compare(password, user.password, function (err, valid) {
                        if (err) { next(err); }
                        if (valid) {
                            return next(null, user, { message: 'Logged In' });
                        } else { return next(null, false, { message: 'Incorrect password'}); }
                    });
                } else { next(null, false, { message: 'User is not activated. Please contact admins.'}); }
            } else { next(null, false, { message: 'Could not find user with email ' + email }); }
        });
       }
     ));
    

In the console, I always get that this is successful.

Console reads:

User logged in:  Object {firstName: "John", lastName: "Doe", email: "[email protected]", activated: true, isUser: true…}
  1. Then, my understanding of what happens gets kind of fuzzy. I know the application then tries to see if the user is authenticated. IsAuthenticated gets triggered, which looks like this in AngularJS:

    isAuthenticated: function (force) {
        var deferred = $q.defer();
    
        if (isAuthenticated && currentUser) {
            deferred.resolve(currentUser);
        } else {
            return baseAuth.customGET('authenticated').then(function (user) {
                deferred.resolve(currentUser);
                isAuthenticated = true;
                currentUser = user;
                return user;
            });
        }
    
        return deferred.promise;
    }
    
  2. It hits this action in the backend UserController

    isAuthenticated: function (req, res) {
       if (req.isAuthenticated() && req.user.isUser) { return res.json(req.user); }
       else { return res.send(401); }
    },
    

Then it fails 🙁 GET http://localhost:1337/user/authenticated 401 (Unauthorized) Neither req.isAuthenticated nor req.user.isUser pass. I have separated them out to individually test and neither of them are true. “isUser” is a value I default to true and is so it should be true for every single user in the db. req.isAuthenticated also fails, which I don’t entirely understand.

Anyone have some insight into my problem? What I have I done wrong here?

Here is Solutions:

We have many solutions to this problem, But we recommend you to use the first solution because it is tested & true solution that will 100% work for you.

Solution 1

If your frontend application has as a different origin than your backend application the AJAX requests will not include the session cookie and req.isAuthenticated() will never returns true.

Use the withCredentials options to force it.

$http({ withCredentials: true, ... })

It seems that Restangular uses the same options:

https://github.com/mgonto/restangular#setdefaulthttpfields

https://docs.angularjs.org/api/ng/service/$http#usage

Edit: You should also check the server side configuration as pointed out by idbehold


As suggested by Matan, you should really give a try to the sails-generate-auth generator on the server side.

https://github.com/sails101/even-more-passport

https://github.com/kasperisager/sails-generate-auth

Solution 2

Usualy a 401 ‘Unauthorized’ error actually refers to a credentials error rather than 403 error which refers to a real Unauthorized error(forbidden) so you should check your client with Postman first to test It and then move on to the client.

although, I would highly recommend that you will drop down from that github project and use this tutorial to implmenet your passport.js authentication as you only need to execute 2 lines of code and some configurations rather then get messy with the code: https://www.bearfruit.org/2014/07/21/tutorial-easy-authentication-for-sails-js-apps/

Solution 3

Since passport.js works using the underlying concept of strategies, you need to make sure that you authenticate using the right one. I don’t see anywhere a strategy called ‘user’

passport.use(new LocalStrategy({ usernameField: 'email', passwordField: 'password' },
function(email, password, next) {
   User.findOne({ email: email })
       .exec(function (err, user) {
           if (err) { return next(err); }
           if (user) {
                if (user.activated) {
                bcrypt.compare(password, user.password, function (err, valid) {
                    if (err) { next(err); }
                    if (valid) {
                        return next(null, user, { message: 'Logged In' });
                    } else { return next(null, false, { message: 'Incorrect password'}); }
                });
            } else { next(null, false, { message: 'User is not activated. Please contact admins.'}); }
        } else { next(null, false, { message: 'Could not find user with email ' + email }); }
    });
   }
 ));

should be changed to

passport.use('user', new LocalStrategy({ usernameField: 'email', passwordField: 'password' },
function(email, password, next) {
   User.findOne({ email: email })
       .exec(function (err, user) {
           if (err) { return next(err); }
           if (user) {
                if (user.activated) {
                bcrypt.compare(password, user.password, function (err, valid) {
                    if (err) { next(err); }
                    if (valid) {
                        return next(null, user, { message: 'Logged In' });
                    } else { return next(null, false, { message: 'Incorrect password'}); }
                });
            } else { next(null, false, { message: 'User is not activated. Please contact admins.'}); }
        } else { next(null, false, { message: 'Could not find user with email ' + email }); }
    });
   }
 ));

Also make sure your user’s isUser flag is set to true.

Note: Use and implement solution 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply