0

I have a AngularJs user authencation built by myself and its working. Everything is working fine. I can login, can check if user has access to a specific page, etc.. Except for the fact the token authentication i's running only on the second interaction i make with the WebApp.

For example, if I change some data on the localStorage (where i'm saving user data) and try to go the an admin page, for example, I'll be able to access that page, but on the next interaction, then I'll get kicked from the page and back to the login process.

What may be happening? This is the code I'm using:

app.js

function getUser() {
    userinfo = JSON.parse(localStorageService.get("user")); //convert string to json
    $scope.userData = userinfo; //Display purpouso only;
};
function checkToken() {
    var userCheckToken = JSON.parse(localStorageService.get("user"));
    $http.post('dist/php/db.php?action=token', userCheckToken)
    .success(function(res){
        userToken = res;
    }).error(function(err){
        alert(feedbackError);
    });
};

$rootScope.$on('$stateChangeStart', function(e, to) {
    if (to.data && to.data.requireLogin) {
        getUser();
        checkToken();
        if (!userinfo) {
            e.preventDefault();
            $state.go('login');
            alert("You need to be loggedin");
        }
        else if (userinfo && !userToken) {
            e.preventDefault();
            userInfo = false;
            $state.go('login');
            localStorageService.clearAll();
            alert('Authentication failed');
        }
    }
});

Same thing happens to an individual function. Let's say i have an important function on the page, so only an admin can execute that function. I'm also checking the authentication on that proccess, but it's happening the same thing, only on the second interaction.

function:

$scope.debugTk = function() {
    checkToken();
    if (!userToken) {
        alert('Authentication failed');
    } else {
        $http.get('dist/php/db.php?action=debugTk')
        .success(function(res){
            $scope.resultDebug = res;
        }).error(function(err){
            alert(feedbackError);
        });
    }
}
celsomtrindade
  • 4,501
  • 18
  • 61
  • 116

2 Answers2

2

as charlieftl already said, your checkToken function uses an XHR request which is asynchronous by default. You need to wrap everything that relies on the execution of checkToken with a callback like this:

function getUser() {
    userinfo = JSON.parse(localStorageService.get("user")); //convert string to json
    $scope.userData = userinfo; //Display purpouso only;
};
function checkToken() {
    var userCheckToken = JSON.parse(localStorageService.get("user"));
    $http.post('dist/php/db.php?action=token', userCheckToken)
    .success(function(res){
        return res; // returns inside a promise make them chainable.
    }).error(function(err){
        return feedbackError;
    });
};

$rootScope.$on('$stateChangeStart', function(e, to) {
    if (to.data && to.data.requireLogin) {
        getUser();
        if (!userinfo) {
            e.preventDefault();
            $state.go('login');
            alert("You need to be loggedin");
        } else {
            checkToken().then(function(userToken){ // this gets executed after checkToken() succeeds.

                if (!userToken) {
                    // this won't work here: e.preventDefault();
                    userInfo = false;
                    $state.go('login');
                    localStorageService.clearAll();
                    alert('Authentication failed');
                }
            }, function(err){
                // this gets called when your .error method returns an error
                // eg. the HTTP request failed.
            });
        }
    }
});

Your debugTk function will look like this then: $scope.debugTk = function() { checkToken().then(function(){ // success }, function(){ // error

        if (!userToken) {
            alert('Authentication failed');
        } else {
            $http.get('dist/php/db.php?action=debugTk')
            .success(function(res){
                $scope.resultDebug = res;
            }).error(function(err){
                alert(feedbackError);
            });
        }
    });
}

Read more about promises here: https://github.com/wbinnssmith/awesome-promises

Edit: The e.preventDefault() call won't work inside the promise, you'll need to change your code to adapt promises. I wouldn't wrap something like this into a $stateChangeStart event, instead use a service to handle all the auth stuff.

posixpascal
  • 3,031
  • 3
  • 25
  • 44
  • getting close but preventing default inside the `$http` callback is too late to prevent the state change – charlietfl Sep 26 '15 at 18:08
  • thanks for the catch. I edit the answer :). Didn't even notice the preventDefault call. – posixpascal Sep 26 '15 at 18:09
  • Thanks @praszyk i understood the concept, but I'm new to programming, so somethings i still need to learn. One thing, i have this error: `Cannot read property 'then' of undefined` on this part here: `checkToken().then(function(userToken){` any idea why? – celsomtrindade Sep 26 '15 at 18:30
1

According to AngularJS documentation:

The $http legacy promise methods success and error have been deprecated. Use the standard then method instead. If $httpProvider.useLegacyPromiseExtensions is set to false then these methods will throw $http/legacy error.

About your question, as it was said the checkToken function is asynchronous, so you need use promises each time you call this function. Return $http.post result from checkToken function:

function checkToken() {
  var userCheckToken = JSON.parse(localStorageService.get("user"));
  return $http.post('dist/php/db.php?action=token', userCheckToken).then(
    function (res) {
      userToken = res;
    },
    function (err) {
      alert(feedbackError);
    });
};

And then use it as regular promise:

$scope.debugTk = function() {
  checkToken().then(function(){
    if (!userToken) {
      alert('Authentication failed');
    } else {
      //.....
    }
  });
}
Evgeny Popov
  • 73
  • 1
  • 8
  • Thank you sr! With your answer and the answer from @praszyk I'm able to put it together and make it work. The only problem so far is with the e.preventDefault() method. Like praszyk said, it won't work inside the promise result. But then, how can i use it so the user won't go to the page he's trying to go or execute the function he's trying to execute? – celsomtrindade Sep 26 '15 at 18:56
  • I think the answer is lying in ui-router documentation. If it was `ngRoute`, I would have navigate user to some default page, like a dashboard or something like that. For functions, I think you can use function `executeIfHaveAccess(action)` (definitely not a proper name for function), which will check whether user can execute this action or not. – Evgeny Popov Sep 26 '15 at 19:14
  • If I'm not wrong, that would (be this)[https://github.com/angular-ui/ui-router/wiki#resolve], right? But then i'll have to move the auth check inside the `.state` to make it work? – celsomtrindade Sep 26 '15 at 19:19
  • 1
    Look at this, http://stackoverflow.com/questions/22537311/angular-ui-router-login-authentication. Hope it would help you – Evgeny Popov Sep 26 '15 at 19:25