AngularJS | handle routing before they load
Asked Answered
M

5

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?

Mara answered 13/9, 2013 at 14:16 Comment(0)
M
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.

Mara answered 14/9, 2013 at 19:46 Comment(10)
I wish you create an example for this using $timeout, to mimic a service call.Arachnid
What is inside Auth.loginCheck(next); ?Septarium
@MidnightGuest this method check the user access for permission to the page, if denied redirectMara
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.Septarium
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)?Idle
hey guys, We'll publish this code as open-source as soon as possible. If you need it asasp, contact me personally.Mara
can u share some working example on plunker, if you have any similar example ?Pathos
@Pathos we are working on it.. I hope we can release it this week.Mara
@Pathos see github.com/GoDisco/ngAuth but BE AWARE- it still under developmentMara
Hey guys. Finally- We published a work version of ngAuth with instructions.. feel free to use it! github.com/GoDisco/ngAuthMara
S
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;
    }
})
Shelbashelbi answered 13/9, 2013 at 14:28 Comment(7)
Would you have to do this on every route or could you put this on a rootScope event?Squeteague
@sasonic, thats exactly my problem! To resolve my problem I have to attach resolve to every route!Mara
@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.Queri
where that ´isAuthenticated´ comes from ?Repertoire
@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.Shelbashelbi
It was not working for me until I added deffered.reject() just before redirectingIllustrational
NOTE: using angular 1.3, i had to wrap the resolve function in an object or it won't get invoked: resolve: { load: function(){...} }Continuous
M
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.

Mara answered 14/9, 2013 at 19:46 Comment(10)
I wish you create an example for this using $timeout, to mimic a service call.Arachnid
What is inside Auth.loginCheck(next); ?Septarium
@MidnightGuest this method check the user access for permission to the page, if denied redirectMara
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.Septarium
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)?Idle
hey guys, We'll publish this code as open-source as soon as possible. If you need it asasp, contact me personally.Mara
can u share some working example on plunker, if you have any similar example ?Pathos
@Pathos we are working on it.. I hope we can release it this week.Mara
@Pathos see github.com/GoDisco/ngAuth but BE AWARE- it still under developmentMara
Hey guys. Finally- We published a work version of ngAuth with instructions.. feel free to use it! github.com/GoDisco/ngAuthMara
N
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

Nodababus answered 13/9, 2013 at 14:26 Comment(1)
Thanks, I'll check this module. About the resolve solution- it force me to attach resolve to every route...Mara
M
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;
            }
        }
Mader answered 28/8, 2014 at 20:30 Comment(0)
G
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

Gobbet answered 13/9, 2013 at 15:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.