0

I am developping an OAuth Provider application, using AngularJS and ui-router. For each state change, I do the following check:

  1. If the user is already logged in: 1.1 If the user is not an admin -> redirect him to the callBackUrl 1.2 If the user is an admin, do nothing

  2. If the user is not logged in: 2.1 If the user tries to access an admin page -> redirect him back to login 2.2 If not, do nothing

my ui-router run method is the following:

.run(function ($rootScope, $state, $auth, accountService, $window, $stateParams) {
    $rootScope.$on('$stateChangeStart', function (e, toState, toParams, fromState, fromParams) {
        accountService.getUser({ token: $auth.getToken() }).$promise
            .then(function (response) {
                if (response.isAdmin === false) {
                    e.preventDefault();
                    $window.location.href = $stateParams.callBackUrl;
                    return;
                }
            })
            .catch(function (response) {
                if (toState.name.split(".")[0] === 'admin') {
                    e.preventDefault();
                    $state.go('root.login');
                }
            });
    });
});

Everything is OK except for the part where I redirectthe user to the callback URL using $window

$window.location.href = $stateParams.callBackUrl;

This redirection takes 2-3 seconds, and in the meantime my user can see the page he is trying to access on my application. I thought the use of preventDefault() would solve that but it doesn't. Do you know how I could hold the $statechangeevent so that the user is redirected directly to the callback URL?

Thanks

Bruno Deprez
  • 418
  • 8
  • 16

2 Answers2

2

I would put it this way:

The above approach allows user everything, until he is not really proven to be UN-authenticated. Why that? Because the code is calling service and evaluating all the stuff once the data are received. Meanwhile - we trust the user.

So, I'd suggest to change the approach

NEVER trust the user. He/she must to do the best to prove he/she is the right one, to get somewhere ... (well, kind of that...)

I described one possible way (with working example) here:

Confusing $locationChangeSuccess and $stateChangeStart

Just a piece of code to cite

Th first part of the $rootScope.$on('$stateChangeStart', ...:

    // if already authenticated...
    var isAuthenticated = userService.isAuthenticated();
    // any public action is allowed
    var isPublicAction = angular.isObject(toState.data)
                       && toState.data.isPublic === true;    


    // here - user has already proved that he is the one
    // or the target is public (e.g. login page)
    // let him go, get out of this check

    if (isPublicAction || isAuthenticated) {
      return;
    }

The second part, user is not trusted, he requires access to private stuff

    // now - stop everything
    // NO navigation
    // we have to be sure who user is, to continue

    // stop state change
    event.preventDefault();

    // async load user 
    userService
       .getAuthObject()
       .then(function (user) {

          var isAuthenticated = user.isAuthenticated === true;

          if (isAuthenticated) {
            // let's continue, use is allowed
            $state.go(toState, toParams)
            return;
          }    
          // log on / sign in...
          $state.go("login");
       })

Check that, in action, here

Community
  • 1
  • 1
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
0

I know this was asked a while ago, but here is a possible solution:

Ui-router actually provides a fantastic way to solve this. By using a "resolve" within your $stateProvider, you can check that the user is authenticated before the controller of that particular state is even instantiated. Here is what ui-router docs say about resolve:

Resolve

You can use resolve to provide your controller with content or data that is custom to the state. resolve is an optional map of dependencies which should be injected into the controller.

If any of these dependencies are promises, they will be resolved and converted to a value before the controller is instantiated and the $stateChangeSuccess event is fired.

https://github.com/angular-ui/ui-router/wiki - Section for resolve is almost half way down page

You can run accountService.getUser within the resolve to check for an authenticated user, and by doing so, would prevent someone who is not authed from seeing the view they are trying to route to.

The resolve is set up inside the $stateProvider, and may look something like this:

$stateProvider.state('myState', {
  resolve: {
    userAuth: function(accountService) {
      return accountService.getUser();
    }
  }
)

If you notice in the above example, I set a property called userAuth within the resolve. This can now be injected into any controller or service and then you can check against it for authenticated users. Each state that needs to be a "protected" view can contain the resolve, and then the 2-3 second flash of the view won't occur, as the controller hasn't been instantiated, and the user is redirected to another state.

snowkid314
  • 55
  • 6