Reputation: 1380
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
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.
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