Search code examples
angularjsangularjs-serviceangularjs-routing

How to check authentication and automatically redirect to login state with ui-router?


so I have this function in my run in angularjs (running AngularJS v1.2.26)

.run(function(....) {
    authService.authCheck();

    $rootScope.$on('$routeChangeStart', function(event, next, current) {
        if (next.requireLogin) {
            if (!authService.isLoggedIn()) {
                $location.path('/login');
                event.preventDefault();
            }
        }
    });

})...

when directly accessing a URL that requires authentication e.g. http://127.0.0.1:8080/secret, the function always redirects to the login path, clearly after debugging it for a while it evaluates to false all the time. when accessing default page then going to the specified route it runs without any problems.

This is part of my authService

this.authCheck = function() {
    $http({
        method: 'PUT',
        url: '/api/login'
    }).
    success(function(data, status, headers, config) {
        if (status === 200) {
            userIsAuthenticated = true;
        } else {
            userIsAuthenticated = false;
        }
        $rootScope.loginStatus = userIsAuthenticated;
    }).
    error(function(data, status, headers, config) {
        userIsAuthenticated = false;
        $rootScope.loginStatus = userIsAuthenticated;
    });
};

this.isLoggedIn = function() {
    return userIsAuthenticated;
};

So every time I have a break point on !authService.isLoggedIn() I see that it evaluates to false even though the function in the run block tries to authenticate it before reaching the $rootScope.$on function.

What exactly is preventing the state of userIsAuthenticated variable from being persistent?


Solution

  • The .authCheck in your authService is asynchronous, which means that userIsAuthenticated won't be set to true until it actually finishes for the first time. If you followed the route you're on now, you'd have to write some more complex code for waiting if the check isn't completed yet and then redirecting. However, that's a messy sub-optimal option.

    The best way to go here is to let ui-router work this out for you. Create a common ancestor state for all your states that require login, e.g. 'session'. Then make that state's resolution wait for your authentication check using resolve option. If not authenticated, create a specific state transfer rejection error, which you can elsewhere catch and act upon (redirect to login).

    .state('session', {
        resolve: {
            authentication: ['authService', '$q', function (authService, $q) {
                return authService.checkAuthentication().then(function () {
                    if (!authService.isAuthenticated) {
                        return $q.reject({
                            notAuthenticated: true
                        });
                    }
                });
            }]
        },
        // ...
    })
    .state('secret', {
        parent: 'session',
        // ...
    });
    

    and then

    $rootScope.$on('$stateChangeError', function (_0, _1, _2, _3, _4, error) {
        if (error.notAuthenticated) {
            $state.go('login');
        }
    });
    

    (you'll need to properly return from your authentication check as well)

    checkAuthentication: function () {
        return $http({...})
            .success(...)
            .error(...);
    }