2

I'm using ui-router (0.2.18) and I want to restrict access to some pages if a user isn't logged in or isn't allowed to see something.

I came up with the solution below, but even though I'm not logged in, I still can see the page for a second, then I get redirected to the /login page. Clearly this is ugly, how can I do the check before loading the page? I'm trying to find a modular solution.

I do backend checks, so if a user isn't logged in, he/she can't see anything anyway, but this redirect thing bugs me.

Factory

angular.module('app.index').factory('Auth', ["$http", "$window", function($http, $window) {
  return {
    isLoggedIn: function() {
      return $http.get('/whoami').then(function(rt) {
        if (rt.data.length) {
          return true;
        }
      }).catch(function(err) {
        return $window.location.href = 'http://localhost/login';
      });
    }
  };
}]);

Routes

angular.module('app.index.routes')
  .run(
    ['$rootScope', '$state', '$stateParams', 'Auth',

      function($rootScope, $state, $stateParams, Auth) {
        $rootScope.$state = $state;
        $rootScope.$stateParams = $stateParams;

        $rootScope.$on("$stateChangeStart", function(event, toState, toParams, fromState, fromParams) {

          if (toState.authenticate && !Auth.isLoggedIn()) {
            console.log("please log in");
            event.preventDefault();
          } else {
            console.log("you are logged in");
          }
        });
      }
    ]
  )

.config(
  ['$stateProvider', '$urlRouterProvider', '$locationProvider',
    function($stateProvider, $urlRouterProvider, $locationProvider) {

      $locationProvider.html5Mode({
        enabled: true,
        requireBase: false
      });

      $stateProvider
        .state('index', {
          url: '/test',
          authenticate: true, // if this is true, `toState.authenticate` becomes true
          template: "auth check"
        });
    }
  ]);

1 Answer 1

4

Here's an example based on an app I have that checks state. The main things to note are:

  • Use data property on state because child states will inherit the data properties of their parents. This will allow the required authentication to propagate to child states for free.

  • Your're checking to see if the user is logged in is using an http request, this is bad because you'll experience delays due to latency. You should use a service variable or session storage to hold a flag for if the user is logged in, that way there won't be a delay when checking the auth status.

  • Using $state.go() instead of setting window.location might be the better solution, since you're using html5 mode it may be interpreting this to do a full page reload instead of a hash change (not sure on this I don't usually use html5 mode).

Hope this helps:

var core = angular.module("core", ["ui.router", "ngStorage"]);
core.run(["$rootScope", "$state", "AuthService", function ($rootScope, $state, Auth) {
    $rootScope.$on("$stateChangeStart", function (evt, toState, toParams, fromState, fromParams) {
        if (toState.name !== "login" &&
            _.get(toState, "data.authenticate", false) &&
            !Auth.isLoggedIn()) {
            $state.go("login");
            evt.preventDefault();
        }
    });
}]);

core.factory("AuthService", ["$http", "$sessionStorage", "$q",
    function ($http, $sessionStorage, $q) {
    return {
        isLoggedIn: function () {
            return !!$sessionStorage.user;
        },
        localLogin: function (email, password) {
            var def = $q.defer();
            $http
                .post("/login", { email: email, password: password })
                .then(function (res) {
                    if (_.has(res, "data.error")) {
                        def.reject(res.data.error);
                    } else {
                        $sessionStorage.user = res.data.user;
                        def.resolve();
                    }
                })
                .catch(function (err) {
                    def.reject(err);
                });
            return def.promise;
        }
    };
}]);

core.config(["$stateProvider", "$urlRouterProvider",
    function ($stateProvider, $urlRouterProvider) {

        // For all unmatched url, redirect to /login
        $urlRouterProvider.otherwise("/login");

        $stateProvider
            .state("login", {
                url: "/login",
                views: {
                    "": {
                        templateUrl: "app/core/templates/login.html",
                        controller: "core.login.ctrl",
                        controllerAs: "vm"
                    }
                }
            })
            .state("profile", {
                url: "/profile",
                data: {
                    authenticate: true
                },
                views: {
                    "": {
                        templateUrl: "app/core/templates/profile.html",
                        controller: "core.ctrl",
                        controllerAs: "vm"
                    }
                }
            });
    }]);
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.