496

I'm writing a small AngularJS app that has a login view and a main view, configured like so:

$routeProvider
 .when('/main' , {templateUrl: 'partials/main.html',  controller: MainController})
 .when('/login', {templateUrl: 'partials/login.html', controller: LoginController})
 .otherwise({redirectTo: '/login'});

My LoginController checks the user/pass combination and sets a property on the $rootScope reflecting this:

function LoginController($scope, $location, $rootScope) {
 $scope.attemptLogin = function() {
   if ( $scope.username == $scope.password ) { // test
        $rootScope.loggedUser = $scope.username;
        $location.path( "/main" );
    } else {
        $scope.loginError = "Invalid user/pass.";
    }
}

Everything works, but if I access http://localhost/#/main I end up bypassing the login screen. I wanted to write something like "whenever the route changes, if $rootScope.loggedUser is null then redirect to /login"

...

... wait. Can I listen to route changes somehow? I'll post this question anyway and keep looking.

Blackhole
  • 20,129
  • 7
  • 70
  • 68
st.never
  • 11,723
  • 4
  • 20
  • 21
  • 3
    Just to clarify: while many solutions below work well, I've recently been more inclined to accept @Oran's answer below -- that is, have the server respond with a 401 code when asked for a sensitive URL, and use that information to control the "login box" on the client. (However the jury is still out on the "queueing denied requests and re-issuing them later" bit, at least for me :) ) – st.never Jun 02 '13 at 00:26

11 Answers11

515

After some diving through some documentation and source code, I think I got it working. Perhaps this will be useful for someone else?

I added the following to my module configuration:

angular.module(...)
 .config( ['$routeProvider', function($routeProvider) {...}] )
 .run( function($rootScope, $location) {

    // register listener to watch route changes
    $rootScope.$on( "$routeChangeStart", function(event, next, current) {
      if ( $rootScope.loggedUser == null ) {
        // no logged user, we should be going to #login
        if ( next.templateUrl != "partials/login.html" ) {
          // not going to #login, we should redirect now
          $location.path( "/login" );
        }
      }         
    });
 })

The one thing that seems odd is that I had to test the partial name (login.html) because the "next" Route object did not have a url or something else. Maybe there's a better way?

st.never
  • 11,723
  • 4
  • 20
  • 21
  • 13
    Cool man, thanks for sharing your solution. One thing to note: in the current version, it's "next.$route.templateUrl" – doubledriscoll Oct 01 '12 at 22:35
  • @st.never seems ur answer will help me in fixing my problem. See here http://stackoverflow.com/questions/13566364/prompt-confirm-before-leaving-edited-html-form I just want to "Cancel" the navigation and keep user on current page intact. – Anand Nov 29 '12 at 04:55
  • Great. I want just to add a link to the Angular API for [Module.run](http://docs.angularjs.org/api/angular.Module) for reference. – Anatoly Mironov May 17 '13 at 10:47
  • 5
    If you look at the network requests in chrome inspector the route that is being redirected (because the user is not logged in) still gets called and a response gets sent to the browser, and then the redirected path '/login' is called. So this method is no good as non-logged-in users can see the response for a route they shouldnt have access to. – sonicboom May 31 '13 at 21:35
  • 35
    Use $locationChangeStart instead of $routeChangeStart to prevent the route from getting called and letting unauthenticated users view content they shouldn't have access to. – sonicboom May 31 '13 at 21:44
  • 18
    Remember that this is the client. There should also be a serverside barrier. – Neikos Sep 23 '13 at 08:06
  • 1
    Thanks for this solution. I've improve it by adding a "requireLogin" field to each route (when) I define and then check if (next.requireLogin) instead of using specific template url. You may have more than one view that does not require login such as home, about and most of all login. – Ido Ran Oct 19 '13 at 20:03
  • 1
    when I did console.log(next.templateUrl) in event callback function scope it says 'undefined'. How We can get next template ? – Arham Ali Qureshi Nov 03 '13 at 01:39
  • @ArhamAliQureshi `$locationChangeStart` 's `next` parameter is a string of absolute url of next url, it's a pain to use. I ended up still using `$routeChangeStart`. – Mark Ni Nov 26 '13 at 20:18
  • @st.never, you could put a `name: 'login'` attribute on your routes. It's what I did with angular-named-routes – airtonix Dec 24 '13 at 07:48
  • 2
    @sonicboom $locationChangeStart doesn't make sense if not all routes require authentication, with $routeChangeStart you can have metadata on the route objects, such as whether or not it is authenticated or what roles are require for that route. Your server should handle not showing unauthenticated content and AngularJS will not start processing until after the route change anyways, so nothing should be shown. – Chris Nicola Jan 02 '14 at 23:01
  • 1
    It appears that it is now `next.$$route`. – parliament Jan 26 '14 at 18:15
  • 1
    @sonicboom can users just bypass all controls by running js in the console to set $rootScope.loggedUser= true? – FutuToad Apr 08 '14 at 13:50
  • I've gone ahead and coded a small node.js/angular app that replicates your code above. It's not doing any server side authentication. But that could easily be added by injecting a service into the logincontroller. It's available for download at: https://github.com/lfernandez55/angularAuthentication – Luke Apr 13 '14 at 06:31
  • 2
    For me the $routeChangeStart event wasn't firing on $rootScope. I'm using Ionic Framework 1.0.0-beta5. I didn't want to use $locationChangeStart because it uses absolute URLs. Instead I use $stateChangeStart which works well and is documented here: https://github.com/angular-ui/ui-router/wiki#state-change-events – Emeka May 31 '14 at 18:42
  • I could not get $routeChangeStart to fire, but $locationChangeStart always does get fired. – Alessandro Nov 17 '14 at 21:04
  • Works for me without the `if ( next.templateUrl ...`, I simply set the location to `/login`, and Angular won't trigger the `$routeChangeStart` event if I'm already at that location – Dmitry Pashkevich Mar 09 '15 at 17:40
  • @IdoRan I think it make more sense to do the inverse of what you said..Add an exclude authentication since this would be the more typical scenario but good comment. Well at least for a web app. I guess for a website, it would make sense to do what you are saying. – KingOfHypocrites Jul 31 '15 at 10:27
  • If `ui.router` is used, use `$stateProvider` instead of `$routeProvider`. – TRiNE Aug 11 '16 at 05:18
94

Here is maybe a more elegant and flexible solution with 'resolve' configuration property and 'promises' enabling eventual data loading on routing and routing rules depending on data.

You specify a function in 'resolve' in routing config and in the function load and check data, do all redirects. If you need to load data, you return a promise, if you need to do redirect - reject promise before that. All details can be found on $routerProvider and $q documentation pages.

'use strict';

var app = angular.module('app', [])
    .config(['$routeProvider', function($routeProvider) {
        $routeProvider
            .when('/', {
                templateUrl: "login.html",
                controller: LoginController
            })
            .when('/private', {
                templateUrl: "private.html",
                controller: PrivateController,
                resolve: {
                    factory: checkRouting
                }
            })
            .when('/private/anotherpage', {
                templateUrl:"another-private.html",
                controller: AnotherPriveController,
                resolve: {
                    factory: checkRouting
                }
            })
            .otherwise({ redirectTo: '/' });
    }]);

var checkRouting= function ($q, $rootScope, $location) {
    if ($rootScope.userProfile) {
        return true;
    } else {
        var deferred = $q.defer();
        $http.post("/loadUserProfile", { userToken: "blah" })
            .success(function (response) {
                $rootScope.userProfile = response.userProfile;
                deferred.resolve(true);
            })
            .error(function () {
                deferred.reject();
                $location.path("/");
             });
        return deferred.promise;
    }
};

For russian-speaking folks there is a post on habr "Вариант условного раутинга в AngularJS."

Ahmed Alnabhan
  • 608
  • 1
  • 9
  • 13
Nikolay Popov
  • 1,106
  • 7
  • 8
  • 1
    why is checkRouting function mapped to factory? Does it matter what it's mapped to? – honkskillet Sep 08 '15 at 16:44
  • @honkskillet: From the angular $routeProvider docs: "factory - {string|function}: If string then it is an alias for a service. Otherwise if function, then it is injected and the return value is treated as the dependency. If the result is a promise, it is resolved before its value is injected into the controller. Be aware that ngRoute.$routeParams will still refer to the previous route within these resolve functions. Use $route.current.params to access the new route parameters, instead." Also from docs on resolve: "If any of the promises are rejected the $routeChangeError event is fired." – Tim Perry Sep 22 '15 at 17:44
  • If `ui.router` is used, use `$stateProvider` instead of `$routeProvider`. – TRiNE Aug 11 '16 at 05:19
61

I have been trying to do the same. Came up with another simpler solution after working with a colleague. I have a watch set up on $location.path(). That does the trick. I am just starting to learn AngularJS and find this to be more cleaner and readable.

$scope.$watch(function() { return $location.path(); }, function(newValue, oldValue){  
    if ($scope.loggedIn == false && newValue != '/login'){  
            $location.path('/login');  
    }  
});
T J
  • 42,762
  • 13
  • 83
  • 138
user1807337
  • 1,070
  • 1
  • 9
  • 20
37

A different way of implementing login redirection is to use events and interceptors as described here. The article describes some additional advantages such as detecting when a login is required, queuing the requests, and replaying them once the login is successful.

You can try out a working demo here and view the demo source here.

Oran Dennison
  • 3,237
  • 1
  • 29
  • 37
  • 3
    Could you please update this answer to include the relevant information from the links? That way it will continue to be useful to visitors even if the links go down. – josliber Jun 29 '16 at 12:53
34

1. Set global current user.

In your authentication service, set the currently authenticated user on the root scope.

// AuthService.js

  // auth successful
  $rootScope.user = user

2. Set auth function on each protected route.

// AdminController.js

.config(function ($routeProvider) {
  $routeProvider.when('/admin', {
    controller: 'AdminController',
    auth: function (user) {
      return user && user.isAdmin
    }
  })
})

3. Check auth on each route change.

// index.js

.run(function ($rootScope, $location) {
  $rootScope.$on('$routeChangeStart', function (ev, next, curr) {
    if (next.$$route) {
      var user = $rootScope.user
      var auth = next.$$route.auth
      if (auth && !auth(user)) { $location.path('/') }
    }
  })
})

Alternatively you can set permissions on the user object and assign each route a permission, then check the permission in the event callback.

AJcodez
  • 31,780
  • 20
  • 84
  • 118
  • @malcolmhall yup, this is opt-in and you want opt-out. Instead add a "public" boolean to public routes like login page, and redirect `if (!user && !next.$$route.public)` – AJcodez Mar 11 '15 at 16:21
  • Could someone please explain `next.$$route` to me? I find nothing in the Angular docs that describe the arguments give to a `$routeChangeStart` event, but I assume `next` and `curr` are some kind of location objects? The `$$route` bit is hard to google. – skagedal Mar 27 '15 at 19:12
  • 2
    I see now that the `$$route` property is a *private variable* of Angular's. You should not rely on that, see for example: http://stackoverflow.com/a/19338518/1132101 - if you do, your code might break when Angular changes. – skagedal Mar 27 '15 at 19:32
  • 2
    I've found a way to access the route without accessing a private property or having to loop through `$route.routes` to build a list (as in @thataustin's answer): get the path for the location with `next.originalPath` and use that to index `$route.routes`: `var auth = $route.routes[next.originalPath]`. – skagedal Mar 27 '15 at 20:56
  • As to answer my question from three comments ago on the arguments that are given to the event - they seem indeed to be undocumented, see this issue which also happens to reference this SO question: https://github.com/angular/angular.js/issues/10994 – skagedal Mar 27 '15 at 20:59
27

Here's how I did it, in case it helps anyone:

In the config, I set a publicAccess attribute on the few routes that I want open to the public (like login or register):

$routeProvider
    .when('/', {
        templateUrl: 'views/home.html',
        controller: 'HomeCtrl'
    })
    .when('/login', {
        templateUrl: 'views/login.html',
        controller: 'LoginCtrl',
        publicAccess: true
    })

then in a run block, I set a listener on the $routeChangeStart event that redirects to '/login' unless the user has access or the route is publicly accessible:

angular.module('myModule').run(function($rootScope, $location, user, $route) {

    var routesOpenToPublic = [];
    angular.forEach($route.routes, function(route, path) {
        // push route onto routesOpenToPublic if it has a truthy publicAccess value
        route.publicAccess && (routesOpenToPublic.push(path));
    });

    $rootScope.$on('$routeChangeStart', function(event, nextLoc, currentLoc) {
        var closedToPublic = (-1 === routesOpenToPublic.indexOf($location.path()));
        if(closedToPublic && !user.isLoggedIn()) {
            $location.path('/login');
        }
    });
})

You could obviously change the condition from isLoggedIn to anything else... just showing another way to do it.

T J
  • 42,762
  • 13
  • 83
  • 138
thataustin
  • 2,035
  • 19
  • 18
9

I'm doing it using interceptors. I have created a library file which can be added to the index.html file. This way you'll have global error handling for your rest service calls and don't have to care about all errors individually. Further down I also pasted my basic-auth login library. There you can see that I also check for the 401 error and redirect to a different location. See lib/ea-basic-auth-login.js

lib/http-error-handling.js

/**
* @ngdoc overview
* @name http-error-handling
* @description
*
* Module that provides http error handling for apps.
*
* Usage:
* Hook the file in to your index.html: <script src="lib/http-error-handling.js"></script>
* Add <div class="messagesList" app-messages></div> to the index.html at the position you want to
* display the error messages.
*/
(function() {
'use strict';
angular.module('http-error-handling', [])
    .config(function($provide, $httpProvider, $compileProvider) {
        var elementsList = $();

        var showMessage = function(content, cl, time) {
            $('<div/>')
                .addClass(cl)
                .hide()
                .fadeIn('fast')
                .delay(time)
                .fadeOut('fast', function() { $(this).remove(); })
                .appendTo(elementsList)
                .text(content);
        };

        $httpProvider.responseInterceptors.push(function($timeout, $q) {
            return function(promise) {
                return promise.then(function(successResponse) {
                    if (successResponse.config.method.toUpperCase() != 'GET')
                        showMessage('Success', 'http-success-message', 5000);
                    return successResponse;

                }, function(errorResponse) {
                    switch (errorResponse.status) {
                        case 400:
                            showMessage(errorResponse.data.message, 'http-error-message', 6000);
                                }
                            }
                            break;
                        case 401:
                            showMessage('Wrong email or password', 'http-error-message', 6000);
                            break;
                        case 403:
                            showMessage('You don\'t have the right to do this', 'http-error-message', 6000);
                            break;
                        case 500:
                            showMessage('Server internal error: ' + errorResponse.data.message, 'http-error-message', 6000);
                            break;
                        default:
                            showMessage('Error ' + errorResponse.status + ': ' + errorResponse.data.message, 'http-error-message', 6000);
                    }
                    return $q.reject(errorResponse);
                });
            };
        });

        $compileProvider.directive('httpErrorMessages', function() {
            return {
                link: function(scope, element, attrs) {
                    elementsList.push($(element));
                }
            };
        });
    });
})();

css/http-error-handling.css

.http-error-message {
    background-color: #fbbcb1;
    border: 1px #e92d0c solid;
    font-size: 12px;
    font-family: arial;
    padding: 10px;
    width: 702px;
    margin-bottom: 1px;
}

.http-error-validation-message {
    background-color: #fbbcb1;
    border: 1px #e92d0c solid;
    font-size: 12px;
    font-family: arial;
    padding: 10px;
    width: 702px;
    margin-bottom: 1px;
}

http-success-message {
    background-color: #adfa9e;
    border: 1px #25ae09 solid;
    font-size: 12px;
    font-family: arial;
    padding: 10px;
    width: 702px;
    margin-bottom: 1px;
}

index.html

<!doctype html>
<html lang="en" ng-app="cc">
    <head>
        <meta charset="utf-8">
        <title>yourapp</title>
        <link rel="stylesheet" href="css/http-error-handling.css"/>
    </head>
    <body>

<!-- Display top tab menu -->
<ul class="menu">
  <li><a href="#/user">Users</a></li>
  <li><a href="#/vendor">Vendors</a></li>
  <li><logout-link/></li>
</ul>

<!-- Display errors -->
<div class="http-error-messages" http-error-messages></div>

<!-- Display partial pages -->
<div ng-view></div>

<!-- Include all the js files. In production use min.js should be used -->
<script src="lib/angular114/angular.js"></script>
<script src="lib/angular114/angular-resource.js"></script>
<script src="lib/http-error-handling.js"></script>
<script src="js/app.js"></script>
<script src="js/services.js"></script>
<script src="js/controllers.js"></script>
<script src="js/filters.js"></script>

lib/ea-basic-auth-login.js

Nearly same can be done for the login. Here you have the answer to the redirect ($location.path("/login")).

/**
* @ngdoc overview
* @name ea-basic-auth-login
* @description
*
* Module that provides http basic authentication for apps.
*
* Usage:
* Hook the file in to your index.html: <script src="lib/ea-basic-auth-login.js">  </script>
* Place <ea-login-form/> tag in to your html login page
* Place <ea-logout-link/> tag in to your html page where the user has to click to logout
*/
(function() {
'use strict';
angular.module('ea-basic-auth-login', ['ea-base64-login'])
    .config(['$httpProvider', function ($httpProvider) {
        var ea_basic_auth_login_interceptor = ['$location', '$q', function($location, $q) {
            function success(response) {
                return response;
            }

            function error(response) {
                if(response.status === 401) {
                    $location.path('/login');
                    return $q.reject(response);
                }
                else {
                    return $q.reject(response);
                }
            }

            return function(promise) {
                return promise.then(success, error);
            }
        }];
        $httpProvider.responseInterceptors.push(ea_basic_auth_login_interceptor);
    }])
    .controller('EALoginCtrl', ['$scope','$http','$location','EABase64Login', function($scope, $http, $location, EABase64Login) {
        $scope.login = function() {
            $http.defaults.headers.common['Authorization'] = 'Basic ' + EABase64Login.encode($scope.email + ':' + $scope.password);
            $location.path("/user");
        };

        $scope.logout = function() {
            $http.defaults.headers.common['Authorization'] = undefined;
            $location.path("/login");
        };
    }])
    .directive('eaLoginForm', [function() {
        return {
            restrict:   'E',
            template:   '<div id="ea_login_container" ng-controller="EALoginCtrl">' +
                        '<form id="ea_login_form" name="ea_login_form" novalidate>' +
                        '<input id="ea_login_email_field" class="ea_login_field" type="text" name="email" ng-model="email" placeholder="E-Mail"/>' +
                        '<br/>' +
                        '<input id="ea_login_password_field" class="ea_login_field" type="password" name="password" ng-model="password" placeholder="Password"/>' +
                        '<br/>' +
                        '<button class="ea_login_button" ng-click="login()">Login</button>' +
                        '</form>' +
                        '</div>',
            replace: true
        };
    }])
    .directive('eaLogoutLink', [function() {
        return {
            restrict: 'E',
            template: '<a id="ea-logout-link" ng-controller="EALoginCtrl" ng-click="logout()">Logout</a>',
            replace: true
        }
    }]);

angular.module('ea-base64-login', []).
    factory('EABase64Login', function() {
        var keyStr = 'ABCDEFGHIJKLMNOP' +
            'QRSTUVWXYZabcdef' +
            'ghijklmnopqrstuv' +
            'wxyz0123456789+/' +
            '=';

        return {
            encode: function (input) {
                var output = "";
                var chr1, chr2, chr3 = "";
                var enc1, enc2, enc3, enc4 = "";
                var i = 0;

                do {
                    chr1 = input.charCodeAt(i++);
                    chr2 = input.charCodeAt(i++);
                    chr3 = input.charCodeAt(i++);

                    enc1 = chr1 >> 2;
                    enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
                    enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
                    enc4 = chr3 & 63;

                    if (isNaN(chr2)) {
                        enc3 = enc4 = 64;
                    } else if (isNaN(chr3)) {
                        enc4 = 64;
                    }

                    output = output +
                        keyStr.charAt(enc1) +
                        keyStr.charAt(enc2) +
                        keyStr.charAt(enc3) +
                        keyStr.charAt(enc4);
                    chr1 = chr2 = chr3 = "";
                    enc1 = enc2 = enc3 = enc4 = "";
                } while (i < input.length);

                return output;
            },

            decode: function (input) {
                var output = "";
                var chr1, chr2, chr3 = "";
                var enc1, enc2, enc3, enc4 = "";
                var i = 0;

                // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
                var base64test = /[^A-Za-z0-9\+\/\=]/g;
                if (base64test.exec(input)) {
                    alert("There were invalid base64 characters in the input text.\n" +
                        "Valid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\n" +
                        "Expect errors in decoding.");
                }
                input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

                do {
                    enc1 = keyStr.indexOf(input.charAt(i++));
                    enc2 = keyStr.indexOf(input.charAt(i++));
                    enc3 = keyStr.indexOf(input.charAt(i++));
                    enc4 = keyStr.indexOf(input.charAt(i++));

                    chr1 = (enc1 << 2) | (enc2 >> 4);
                    chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
                    chr3 = ((enc3 & 3) << 6) | enc4;

                    output = output + String.fromCharCode(chr1);

                    if (enc3 != 64) {
                        output = output + String.fromCharCode(chr2);
                    }
                    if (enc4 != 64) {
                        output = output + String.fromCharCode(chr3);
                    }

                    chr1 = chr2 = chr3 = "";
                    enc1 = enc2 = enc3 = enc4 = "";

                } while (i < input.length);

                return output;
            }
        };
    });
})();
Christopher Armstrong
  • 3,477
  • 6
  • 34
  • 40
  • 2
    You really should stay away from doing dom manipulation in the JS unless your in a directive. If you just set up your logic and then use ng-class to apply a class and trigger a CSS animation you'll thank yourself later. – Askdesigners May 22 '14 at 20:36
8

In your app.js file:

.run(["$rootScope", "$state", function($rootScope, $state) {

      $rootScope.$on('$locationChangeStart', function(event, next, current) {
        if (!$rootScope.loggedUser == null) {
          $state.go('home');
        }    
      });
}])
Ben Cochrane
  • 3,317
  • 1
  • 14
  • 16
4

It's possible to redirect to another view with angular-ui-router. For this purpose, we have the method $state.go("target_view"). For example:

 ---- app.js -----

 var app = angular.module('myApp', ['ui.router']);

 app.config(function ($stateProvider, $urlRouterProvider) {

    // Otherwise
    $urlRouterProvider.otherwise("/");

    $stateProvider
            // Index will decide if redirects to Login or Dashboard view
            .state("index", {
                 url: ""
                 controller: 'index_controller'
              })
            .state('dashboard', {
                url: "/dashboard",
                controller: 'dashboard_controller',
                templateUrl: "views/dashboard.html"
              })
            .state('login', {
                url: "/login",
                controller: 'login_controller',
                templateUrl: "views/login.html"
              });
 });

 // Associate the $state variable with $rootScope in order to use it with any controller
 app.run(function ($rootScope, $state, $stateParams) {
        $rootScope.$state = $state;
        $rootScope.$stateParams = $stateParams;
    });

 app.controller('index_controller', function ($scope, $log) {

    /* Check if the user is logged prior to use the next code */

    if (!isLoggedUser) {
        $log.log("user not logged, redirecting to Login view");
        // Redirect to Login view 
        $scope.$state.go("login");
    } else {
        // Redirect to dashboard view 
        $scope.$state.go("dashboard");
    }

 });

----- HTML -----

<!DOCTYPE html>
<html>
    <head>
        <title>My WebSite</title>

        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <meta name="description" content="MyContent">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <script src="js/libs/angular.min.js" type="text/javascript"></script>
        <script src="js/libs/angular-ui-router.min.js" type="text/javascript"></script>
        <script src="js/app.js" type="text/javascript"></script>

    </head>
    <body ng-app="myApp">
        <div ui-view></div>
    </body>
</html>
T J
  • 42,762
  • 13
  • 83
  • 138
Jesús Castro
  • 2,061
  • 1
  • 22
  • 26
3

If you do not want to use angular-ui-router, but would like to have your controllers lazy loaded via RequireJS, there are couple of problems with event $routeChangeStart when using your controllers as RequireJS modules (lazy loaded).

You cannot be sure the controller will be loaded before $routeChangeStart gets triggered -- in fact it wont be loaded. That means you cannot access properties of next route like locals or $$route because they are not yet setup.
Example:

app.config(["$routeProvider", function($routeProvider) {
    $routeProvider.when("/foo", {
        controller: "Foo",
        resolve: {
            controller: ["$q", function($q) {
                var deferred = $q.defer();
                require(["path/to/controller/Foo"], function(Foo) {
                    // now controller is loaded
                    deferred.resolve();
                });
                return deferred.promise;
            }]
        }
    });
}]);

app.run(["$rootScope", function($rootScope) {
    $rootScope.$on("$routeChangeStart", function(event, next, current) {
        console.log(next.$$route, next.locals); // undefined, undefined
    });
}]);

This means you cannot check access rights in there.

Solution:

As loading of controller is done via resolve, you can do the same with your access control check:

app.config(["$routeProvider", function($routeProvider) {
    $routeProvider.when("/foo", {
        controller: "Foo",
        resolve: {
            controller: ["$q", function($q) {
                var deferred = $q.defer();
                require(["path/to/controller/Foo"], function(Foo) {
                    // now controller is loaded
                    deferred.resolve();
                });
                return deferred.promise;
            }],
            access: ["$q", function($q) {
                var deferred = $q.defer();
                if (/* some logic to determine access is granted */) {
                    deferred.resolve();
                } else {
                    deferred.reject("You have no access rights to go there");
                }
                return deferred.promise;
            }],
        }
    });
}]);

app.run(["$rootScope", function($rootScope) {
    $rootScope.$on("$routeChangeError", function(event, next, current, error) {
        console.log("Error: " + error); // "Error: You have no access rights to go there"
    });
}]);

Note here that instead of using event $routeChangeStart I'm using $routeChangeError

Ivan Hušnjak
  • 3,493
  • 3
  • 20
  • 30
-4
    $routeProvider
 .when('/main' , {templateUrl: 'partials/main.html',  controller: MainController})
 .when('/login', {templateUrl: 'partials/login.html', controller: LoginController}).
 .when('/login', {templateUrl: 'partials/index.html', controller: IndexController})
 .otherwise({redirectTo: '/index'});
  • This is a basic route configuration... Where are we checking any condition before redirecting to the configured routes..? – T J Dec 24 '15 at 06:11