10

I am trying to do some authentication with AngularUI Router. $urlRouter.sync() looks like exactly what I need. However, that's only available when I intercept $locationChangeSuccess. But when I do that, $state.current.name is empty, whereas I want it to be the current state.

Here's my code so far:

$rootScope.$on('$locationChangeSuccess', function(event, next, nextParams) {
  event.preventDefault();
  if ($state.current.name === 'login') {
    return userService.isAuthenticated().then(function(response) {
      var authenticated;
      authenticated = response.authenticated;
      return alert(authenticated);
    });
  }
});

Any pointers as to what I'm doing wrong?

Shamoon
  • 41,293
  • 91
  • 306
  • 570
  • Err... What about `$stateChangeSuccess`? – naeramarth7 Sep 16 '14 at 15:06
  • Then I can't use `urlRouter.sync()` – Shamoon Sep 16 '14 at 22:04
  • `$state.current.name` will only be empty on the initial page load. Any state changes triggered after that will have a state name. If all you need to do is enforce authentication, then listen on `$stateChangeStart` - you don't need to worry about `$urlRouter.sync()`. There are plenty of examples around that demonstrate that. – IanB Oct 08 '14 at 06:45
  • Can you share what is the requirement?? – harishr Oct 30 '14 at 14:21
  • @IanB - "There are plenty of examples around that demonstrate that." where? – Jason Oct 23 '15 at 17:56

1 Answers1

34

I would suggest to go more "UI-Router way". We should use $rootScope.$on('$stateChangeStart' event where $state.current would be properly provided. Here is a working example

Let's observe simple (but not naive) solution, which could be extended to any degree later. Also if you will like this approach, here is much more comprehensive implementation: angular ui-router login authentication

Firstly, let's have our user service defined like this:

.factory('userService', function ($timeout, $q) {

    var user = undefined;

    return {
        // async way how to load user from Server API
        getAuthObject: function () {
            var deferred = $q.defer();

            // later we can use this quick way -
            // - once user is already loaded
            if (user) {
                return $q.when(user);
            }

            // server fake call, in action would be $http
            $timeout(function () {
                // server returned UN authenticated user
                user = {isAuthenticated: false };
                // here resolved after 500ms
                deferred.resolve(user)
            }, 500)

            return deferred.promise;
        },

        // sync, quick way how to check IS authenticated...
        isAuthenticated: function () {
            return user !== undefined
                && user.isAuthenticated;
        }
    };    
})

So, we use async (here $timeout) to load user object form a server. In our example it will have a property {isAuthenticated: false }, which will be used to check if is authenticated.

There is also sync method isAuthenticated() which, until user is loaded and allowed - always returns false.

And that would be our listener of the '$stateChangeStart' event:

.run(['$rootScope', '$state', 'userService',
 function ($rootScope, $state, userService) {

     $rootScope.$on('$stateChangeStart', function (event, toState,   toParams
                                                        , fromState, fromParams) {    
        // if already authenticated...
        var isAuthenticated = userService.isAuthenticated();
        // any public action is allowed
        var isPublicAction = angular.isObject(toState.data)
                           && toState.data.isPublic === true;    

        if (isPublicAction || isAuthenticated) {
          return;
        }

        // 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");
           })
       ...

What we are checking first, is if user is already loaded and authenticated (var isAuthenticated = ...). Next we will give green to any public method. This is done with the data {} property of the state object definition (see Attach Custom Data to State Objects)

And that's it. In case of states defined like in a below snippet we can experience:

  • the 'public', 'home' are allowed to anybody
  • the 'private', 'private' will redirect to login if isAuthenticated === false
  • the 'login' in this example provides quick way how to switch isAuthenticated on/off

    // States
    $stateProvider
    
      // public
      .state('home', {
          url: "/home",
          templateUrl: 'tpl.html',
          data: { isPublic: true },
      })
      .state('public', {
          url: "/public",
          templateUrl: 'tpl.html',
          data: { isPublic: true },
      })
      // private
      .state('private', {
          url: "/private",
          templateUrl: 'tpl.html',
      })
      .state('private2', {
          url: "/private2",
          templateUrl: 'tpl.html',
      })
    
      // login
      .state('login', {
          url: "/login",
          templateUrl: 'tpl.html',
          data: { isPublic: true },
          controller: 'loginCtrl',
      })
    

Check that all here

Some other resources:

Community
  • 1
  • 1
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
  • I'm wondering.. maybe doing an http request per time the route changes is to much traffic, wouldn't be better set an auth user object part of windows or a service to see if the user is aunthenticated ? – rahpuser Nov 05 '14 at 03:53
  • If I understand your issue properly, good news is **solved** ;) Angular `provider` model *(including `services` and `factories`)* is driven by **singleton** pattern. So the **`userService`** in our example would be instantiated only **once**, only once will call the server *(here the `$timeout`)* and any next call to it will return `user` local variable... does it help? – Radim Köhler Nov 05 '14 at 06:10
  • ops.. yes, sorry I got confuse with getAuthObj and isAuth.. thanks – rahpuser Nov 05 '14 at 06:13