Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS - prevent not authenticated user from accessing given routes

In my app when user is logged in I have authService which sets internal flag isAuthenticated. Now on every route change I have listener attached to $routeChangeStart event which checks authService.isAuthenticated(). If no it should redirect to login route.

Problem is when user makes page refresh (all the authService settings are lost) and it gets back to login again (while still having valid session on server). This is not what I want.

What I'd like to do is to "block" route change until I get the info if user is authenticated (either from authService which would be immediate, or from server if no info is available in authService, e.g. after refresh). I have such function in authService

        // returns promise
        currentUser: function() {
            if (authService.isAuthenticated()) {
                return $q.when(authService.loggedUser);
            }
            return $http.get('/session').then(function(response) {
                authService.loggedUser = response.user;
                return $q.when(authService.loggedUser);
            });
        }

and would like to use it in event listener.

    $rootScope.$on("$routeChangeStart", function (event, next, current) {
        if(isRouteRestricted(next)) {
            authService.currentUser().then(null, function() {
                $location.path('/login');
            });
        }
    });

The thing is that it doesn't work as expected. I still get the target route visible for a very short time, and then user gets redirected. I believe it is due to nature of promises, but how to get rid of this "blink" effect?

like image 362
Michal Ostruszka Avatar asked Apr 09 '13 15:04

Michal Ostruszka


2 Answers

I'd do something like this in the top level controller, which would be the first controller that's called when the page is refreshed (apologize for typos in the js, I'm a coffeescript guy):

var authCheck = function (event, next, current) {
    if(isRouteRestricted(next)) {
        authService.currentUser().then(null, function() {
            $location.path('/login');
        });
    }
}

authCheck(null, populateNextSomehow).then(function () {
    // all of your controller code, probably in a separate function
});

$rootScope.$on("$routeChangeStart", authCheck);

This will ensure that the controller code cannot be called until the authCheck is complete.

like image 56
Roy Truelove Avatar answered Sep 28 '22 20:09

Roy Truelove


To prevent a user for accessing routes you have to do several things:

First, set your routes and add a property like 'access' : allowAnonymous : true or false

// in app.js
var myApp = angular.module('myApp',['ngResource', 'ngCookies', 'ngRoute']);       
myApp.config(function ($httpProvider, $routeProvider) {
   window.routes = {
        '/Login':
           { templateUrl: '/Account/Login',
             controller: 'AccountController',
             access : {allowAnonymous : true}
           },
        '/MembersPage':
           { templateUrl: '/Home/SomePage,
             controller: SomePageController',
             access: {allowAnonymous:false}
           }
         };

        for (var path in window.routes) {
           $routeProvider.when(path, window.routes[path]);
    }
    $routeProvider.otherwise({ redirectTo: '/Login' });
});   

Second, you must recognize an authenticated user:

There are several ways for doing that but I prefer using the power of AngularJS throughout the use of ‘Services’. Therefore, I have created a ‘UserService’ where we store the current user name and a value-indicating if is authenticated or not.

// in UserService.js
myApp.factory('userService', function () {
var user = {
    isLogged: false,
    username: '',       
};

var reset = function() {
    user.isLogged = false;
    user.username = '';
};

return {
    user: user,
    reset : reset
  };
});

Last thing, capture route changes events and treat them correspondingly:

After we have the service in place, it is time to use it and implement the check functionality for a route. There are several methods that intercept the route change event, but we are interested only in those that occur before the user was redirected so we can check if is authenticated: ‘$routeChangeStart’, ‘$locationChangeStart’. Here we can check if the route that the user is going to allows anonymous access and if the user is logged in. If the case of failure, we can display an error message and redirect the user to the login page.

// in RootController.js
myApp.controller('RootController',
function ($scope, $route, $routeParams, $location, $rootScope, authenticationService,   
userService, toaster) {
 $scope.user = userService.user;
 $scope.$on('$routeChangeStart', function (e, next, current) {               
     if (next.access != undefined && !next.access.allowAnonymous && !$scope.user.isLogged) {
                $location.path("/Login");                   
            }
        });

        $scope.logout = function () {
            authenticationService.logout()
                .success(function (response) {
                    userService.reset();                       
                    toaster.pop("info", 'You are logged out.', '');
                });
        };

 $rootScope.$on("$locationChangeStart", function (event, next, current) {
  for (var i in window.routes) {
    if (next.indexOf(i) != -1) {
     if (!window.routes[i].access.allowAnonymous && !userService.user.isLogged) {
          toaster.pop("error", 'You are not logged in!', '');
             $location.path("/Login");                                                 
                    }
                }
            }
        });
    });

Complete article is here: http://net-daylight.blogspot.ro/

Hope it helps!

like image 45
Leo Avatar answered Sep 28 '22 18:09

Leo