Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access scope or service in ui router onEnter to check access (auth)

Ok, so I have a state for the url "/securepage" which I need to check whenever a user tries to access it. So I read there is an onEnter function I can use. But I cannot seem to get hold on the scope nor a service from there. What am I doing wrong?

        .state('securepage', {
            url: "/securepage",
            template: securepage.html,
            onEnter: function(){
                // if (!$scope.main.isAuthenticated) $state.go("/login");
                // if (!myLoginService.currentUser()) $state.go("/login");

Current options as I see it are to use resolve and/or check the authentication in the controller. But wouldnt an auth check be better placed in onEnter?

like image 589
joeriks Avatar asked Oct 13 '13 18:10

joeriks


2 Answers

I ran into a similar problem today. Spent a whole day and finally came up with a workable solution other than the ones already suggested here.

My main goal is to find easy and effective way to selectively secure certain specific webpages. The security check needs to be performed before the HTML or any of the relevant controllers are loaded or called. If the check fails, the page may be forwarded to elsewhere without any side effects from the other controllers.

I tried the other suggested approaches. Each one has their own set of problems:

  1. Using OnEnter:

    • There is no way to block ui-router from continuing with the state transition while making an async call to do the security check.
  2. Using $rootScope.$on('$stateChangeStart'):

    • The management of the states that rquire security check will become separated from the $stateProvider.state() definitions. Ideally, I would rather see everything about the definition of a state defined all in one place. Though this is not a showstopper, it is not ideal.
    • A much bigger problem is that the $stateChangeStart event is not being called for the initial loading of a page. This one is a showstopper.

My solution is to use a resolve function to define a promise that will cause the view controllers to wait for the deferred to complete before they are called. This work perfectly for blocking the controller from starting in an asynchronous fashion.

Here's a rough outline of the code I used:

.config(['$stateProvider', function ($stateProvider) {
    // Handler for Restricting Access to a page using the state.resolve call
    var accessRestrictionHandler = function($q, $rootScope, $state) {
            var deferred = $q.defer();

            // make sure user is logged in
            asyncCheckForLogin(function(status) {
                if (status != "Logged In") {
                    // You may save target page URL in cookie for use after login successful later
                    // To get the relative target URL, it is equal to ("#" + this.url).  
                    //      The "this" here is the current scope for the parent state structure of the resolve call.
                    $state.go("loginPage");
                }
                else    // if logged in, continue to load the controllers.  Controllers should not start till resolve() is called.
                    deferred.resolve();
            }.bind(this));

            return deferred.promise;
        };


    $stateProvider
        .state('userProfile', {
            url: '/userProfile',
            views: {
                'main': {
                    templateUrl: 'userProfile.html',
                    controller: 'userProfileCtrl'
                }
            },

            // SIMPLY add the line below to all states that you want to secure
            resolve: { loginRequired : accessRestrictionHandler } 
        })

        .state(.... some other state)
        .state(.... some other state);

}]);

I hope this will help some of you out there.

like image 95
Joe H Avatar answered Nov 20 '22 19:11

Joe H


Another approach would be to have a service/controller listen to the "$stateChangeStart" event. There, you could check if the called state needs authentication and reroute the request. Here's a snippet:

$rootScope.$on('$stateChangeStart', function (event, nextState, currentState) {
    if (!isAuthenticated(nextState)) {
        console.debug('Could not change route! Not authenticated!');
        $rootScope.$broadcast('$stateChangeError');
        event.preventDefault();
        $state.go('login');
    }
});

isAuthenticated could hold the call to your services, check nextState.data for authentication-related properties, etc.

like image 14
Phil Thomas Avatar answered Nov 20 '22 19:11

Phil Thomas