17

I wish to create a simple authentication check for my routes by external service.

I define the access requirements on the route object:

$routeProvider
    .when('/', {
        templateUrl: 'src/app/views/index.html',
        controller: 'indexCtrl',
        authenticated: true
    })
    .when('/login', {
        templateUrl: 'src/app/views/login.html',
        controller: 'loginCtrl',
        anonymous:  true
    })
    .otherwise({
        redirectTo: '/'
    })
;

Then, I check if I have permission within the $routeChangeStart event.

$rootScope.$on('$routeChangeStart', function (event, next) {
    if(next.authenticated && !$myService.isLoggedin())
        $location.path("/login");
    else if(next.anonymous && $myService.isLoggedin())
        $location.path("/secured");
});

Actually, it works-
if the user in not authenticated it move him to the login page, if he is authenticated but the route is for anonymous users only it move them to another page, and etc..

BUT- this redirection actually happening after the controllers and the templates is load! And it cause my controller to do some unnecessary request to my REST API, even if I'm not authenticated.

How can I handle the route before they process?

Almog Baku
  • 810
  • 1
  • 9
  • 22

5 Answers5

23

Use $routeProvider resolve

.when('/', {
    templateUrl: 'src/app/views/index.html',
    controller: 'indexCtrl',
    resolve: function($q, $location) {
      var deferred = $q.defer(); 
      deferred.resolve();
      if (!isAuthenticated) {
         $location.path('/login');
      }

      return deferred.promise;
    }
})
codef0rmer
  • 10,284
  • 9
  • 53
  • 76
  • 3
    Would you have to do this on every route or could you put this on a rootScope event? – sasonic Sep 13 '13 at 14:39
  • 1
    @sasonic, thats exactly my problem! To resolve my problem I have to attach `resolve` to every route! – Almog Baku Sep 13 '13 at 15:14
  • 2
    @AlmogBaku you have to attache resolve to every route to make it work. What happens in a rootScope event callback is just a callback, it cannot stop what will happen. But route will not display before all the resolves are resolved. To make this realistic, you can define a function and attach the same function to every route resolves. – Daiwei Sep 13 '13 at 20:42
  • where that ´isAuthenticated´ comes from ? – Merlin May 14 '14 at 11:31
  • @Lorenzo: It's just a placeholder to keep track of whether user is logged in or not. So once you login, just set it to true. – codef0rmer May 14 '14 at 19:11
  • It was not working for me until I added deffered.reject() just before redirecting – sam Sep 25 '14 at 15:04
  • 5
    NOTE: using angular 1.3, i had to wrap the resolve function in an object or it won't get invoked: `resolve: { load: function(){...} }` – schellmax Nov 13 '14 at 12:02
16

My solution was combining $locationChangeStart and $routeChangeStart:

$rootScope.$on('$locationChangeStart', function (event) {
    //If login data not available, make sure we request for it
    if($facebook.isConnected()==null) {
        $facebook.getLoginStatus();
        event.preventDefault();
        return;
    }

    var next=parseRoute().$$route;
    if(!Auth.loginCheck(next))
        event.preventDefault();
});

I copied parseRoute() from angular-route.js to parse the given URL to route..

Then I build my login check handler(Auth.loginCheck) in a way that if it fail it return false.

I also use $routeChangeStart to handle $route.reload() events, so now after every change within my authentication status I just do $route.reload():

$rootScope.$on('$routeChangeStart', function (event, next) {
    Auth.loginCheck(next);
});

Finally I just make sure that this custom service is always will run by using simple run() method.

Edit:

We now using ngAuth, a module we designed to solve that exact problem(based on the answer I gave here before).

At last, we developed a angular-module that solved this issue.. This module is based on the answer I published here before.

Due the requests here, we published a beta release that works now: http://github.com/GoDisco/ngAuth

Feel free to use it.

Almog Baku
  • 810
  • 1
  • 9
  • 22
  • I wish you create an example for this using $timeout, to mimic a service call. – allenhwkim Jan 13 '14 at 18:47
  • What is inside Auth.loginCheck(next); ? – Midnight Guest Jan 24 '14 at 16:56
  • @MidnightGuest this method check the user access for permission to the page, if denied redirect – Almog Baku Jan 24 '14 at 21:45
  • Could you describe in detail how have you copied the parseRoute()? What I see is that I have to copy half of the angular-route.js also. – Midnight Guest Jan 27 '14 at 12:25
  • Hey @AlmogBaku, where do you use $route.reload(), can't see it from above. I'm also trying to achieve similar behavior, with preventing routes and redirecting user. What I have so far, is done with routeChangeStart (but it just does redirecting, and I can't prevent route to be requested from server). Guess that locationChangeStart can help me with that. Can you provide some more details (or jsfiddle)? – Ned Jan 27 '14 at 13:53
  • 1
    hey guys, We'll publish this code as open-source as soon as possible. If you need it asasp, contact me personally. – Almog Baku Jan 27 '14 at 14:25
  • can u share some working example on plunker, if you have any similar example ? – Jaimin Mar 14 '14 at 09:48
  • 1
    @Jaimin we are working on it.. I hope we can release it this week. – Almog Baku Mar 14 '14 at 21:54
  • 2
    @Jaimin see https://github.com/GoDisco/ngAuth but BE AWARE- it still under development – Almog Baku Mar 16 '14 at 11:54
  • Hey guys. Finally- We published a work version of ngAuth with instructions.. feel free to use it! https://github.com/GoDisco/ngAuth – Almog Baku Nov 24 '14 at 13:24
1

Try using resolve property of the route. It resolves all the functions/dependencies passed to it before any controller or template is loaded. Incase the dependency returns a promise, till its resolved nothing is loaded.

Try passing your authentication service in resolve and redirect incase of auth failure.

Please have a look -> https://groups.google.com/forum/#!topic/angular/QtO8QoxSjYw

$stateProvider uses $routeProvider underneath. This wiki will give you more insights. https://github.com/angular-ui/ui-router/wiki#resolve

Buhake Sindi
  • 87,898
  • 29
  • 167
  • 228
Selvam Palanimalai
  • 1,550
  • 1
  • 10
  • 13
  • Thanks, I'll check this module. About the `resolve` solution- it force me to attach `resolve` to every route... – Almog Baku Sep 13 '13 at 15:15
1

Angularjs resolve example:

.when('/profile', {
        templateUrl: 'views/profile.html',
        controller: 'ProfileCtrl',
        resolve: {
            app: function($q, $rootScope, $location) {
                var defer = $q.defer();
                if ($rootScope.currentUser == undefined) {
                    $location.path('/login');
                };
                defer.resolve();
                return defer.promise;
            }
        }
lsampaio
  • 908
  • 7
  • 7
0

Angular-http-auth allow you to handle very elegantly authorization on HTTP level (when fetching template) and prompt for login if needed. All that without even loading template (nor controller) if authorization is denied. Clearly the best thing I've seen so far.

https://github.com/witoldsz/angular-http-auth

PowerKiKi
  • 4,539
  • 4
  • 39
  • 47