12

I have just started out working with an AngularJS app I'm developing, everything is going well but I need a way of protecting routes so that a user wouldn't be allowed to go to that route if not logged in. I understand the importance of protecting on the service side also and I will be taking care of this.

I have found a number of ways of protecting the client, one seems to use the following

$scope.$watch(
    function() {
        return $location.path();
    },
    function(newValue, oldValue) {
        if ($scope.loggedIn == false && newValue != '/login') {
            $location.path('/login');
        }
    }
);

where do I need to put this, in the .run in the app.js?

And the other way I have found is using a directive and using an on - routechagestart

the info is here http://blog.brunoscopelliti.com/deal-with-users-authentication-in-an-angularjs-web-app

I would really be interested in anyones help and feedback on the recommended way.

Robert Koritnik
  • 103,639
  • 52
  • 277
  • 404
Martin
  • 23,844
  • 55
  • 201
  • 327
  • 1
    http://www.egghead.io/ (free) videos 27->39 explain the whole routing thing. It should help you. The closest video is Resolve (35) – Utopik Jun 20 '13 at 09:08
  • Thanks Utopik, Yes i have already seen those. I think I am really looking for some input on the recommended way of doing the above. – Martin Jun 20 '13 at 09:18

2 Answers2

24

Using resolves should help you out here: (code not tested)

angular.module('app' []).config(function($routeProvider){
    $routeProvider
        .when('/needsauthorisation', {
            //config for controller and template
            resolve : {
                //This function is injected with the AuthService where you'll put your authentication logic
                'auth' : function(AuthService){
                    return AuthService.authenticate();
                }
            }
        });
}).run(function($rootScope, $location){
    //If the route change failed due to authentication error, redirect them out
    $rootScope.$on('$routeChangeError', function(event, current, previous, rejection){
        if(rejection === 'Not Authenticated'){
            $location.path('/');
        }
    })
}).factory('AuthService', function($q){
    return {
        authenticate : function(){
            //Authentication logic here
            if(isAuthenticated){
                //If authenticated, return anything you want, probably a user object
                return true;
            } else {
                //Else send a rejection
                return $q.reject('Not Authenticated');
            }
        }
    }
});
Kristian
  • 21,204
  • 19
  • 101
  • 176
Clark Pan
  • 6,027
  • 1
  • 22
  • 18
  • 1
    Thanks, so if not authenticated where would i do a $location.path ? to redirect – Martin Jun 20 '13 at 10:47
  • You can do that via the $routeChangeError event thats broadcasted against the route scope when a route fails. I'll update my answer – Clark Pan Jun 26 '13 at 05:19
  • Is there a way to do this globally for all routes? – AndrewMcLagan Jul 06 '13 at 15:01
  • 1
    @AndrewMcLagan Have a look at http://docs.angularjs.org/api/ng.$http#responseinterceptors – Clark Pan Jul 09 '13 at 07:13
  • -1 - the question was specifically about authorization, not authentication. I would use route/state change events. Add the required "role" to the route provider data (custom attribute) and do the test in the event – Jason Sep 07 '14 at 16:01
  • Why can't I just put a $location.path('/login') in the else block of AuthService? I don't understand how .run is being used here. I thought that was to provide initialization information for a module. I get that failing to resolve a promise from $route will cause $routeChangeError to be broadcast, but I don't understand how that propagates to the function inside .run(). It seems a layer removed since .run() doesn't take in the rejection as a parameter. Is the $routeChangeError accessible inside .run() because it's broadcasting to the rootscope? – user137717 Aug 30 '15 at 19:40
  • That's what I was looking for. but I had to use throw new Error() rather $q.reject.. – kunj choksi Jun 11 '21 at 18:38
4

Another way of using the resolve attribute of the $routeProvider:

angular.config(["$routeProvider",
function($routeProvider) {

  "use strict";

  $routeProvider

  .when("/forbidden", {
    /* ... */
  })

  .when("/signin", {
    /* ... */
    resolve: {
      access: ["Access", function(Access) { return Access.isAnonymous(); }],
    }
  })

  .when("/home", {
    /* ... */
    resolve: {
      access: ["Access", function(Access) { return Access.isAuthenticated(); }],
    }
  })

  .when("/admin", {
    /* ... */
    resolve: {
      access: ["Access", function(Access) { return Access.hasRole("ADMIN"); }],
    }
  })

  .otherwise({
    redirectTo: "/home"
  });

}]);

This way, if Access does not resolve the promise, the $routeChangeError event will be fired:

angular.run(["$rootScope", "Access", "$location",
function($rootScope, Access, $location) {

  "use strict";

  $rootScope.$on("$routeChangeError", function(event, current, previous, rejection) {
    if (rejection == Access.UNAUTHORIZED) {
      $location.path("/login");
    } else if (rejection == Access.FORBIDDEN) {
      $location.path("/forbidden");
    }
  });

}]);

See the full code on this answer.

Community
  • 1
  • 1
sp00m
  • 47,968
  • 31
  • 142
  • 252