salep
salep

Reputation: 1380

Angular ui-router immediate redirect if user is not authenticated

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

Upvotes: 2

Views: 1964

Answers (1)

Michael Cebrian
Michael Cebrian

Reputation: 61

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

Upvotes: 4

Related Questions