I am having an app that every page except login requires authentication. I do this by checking the $stateChangeStart and then redirecting to /login when token is not set. on initial app load this works fine, but at login screen if i type another "restricted" url. It does the check, changes the url, but still loads the restricted page. What am I missing here?
//app.run:
app.lazy = $couchPotato;
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
$rootScope.$on('$stateChangeStart', function(event, toState, toStateParams) {
console.log("state changed to "+toState.name);
console.log(toState);
Authentication.validateLogin(toState, toStateParams);
});
Authentication.validateLogin:
validateLogin:function(toState, toStateParams){
$rootScope.toState = toState;
$rootScope.toStateParams = toStateParams;
if ($localStorage.token == null) {
console.error("Not Authenticated!");
$location.url('/login');
}
}
I see that your using angular-ui, so I'm not exactly sure what advantages that has over using 'basic' angular, but I wrote this to handle validating a token when the route changes.
app.run(['$rootScope', 'authService',
function ($rootScope, authService) {
$rootScope.$on("$routeChangeSuccess", function (event, next, current) {
if (next && next.$$route && authService.isAuthenticated()) {
authService.validate();
$rootScope.appTitle = next.$$route.title;
}
});
}]);
The $routeChangeSuccess handles navigation to routes after the controller has loaded (very important for objects that load when the page loads when validation is confirmed), and then validates them.
This also performs 2 checks in that it checks if the token in local storage exists and is formed correctly, and if it is, send it back to the server to confirm that.
I also found that I had to use the $locationChangeStart to handle page refresh, to re-validate when someone tries to refresh the page.
The validateLogin solution:
validateLogin:function(toState, toStateParams){
if(toState.access){
if(toState.access.loginRequired){
if ($localStorage.token == null) {
$state.go('login');
}
}
}
Try this with the validateLogin:
validateLogin:function(toState, toStateParams){
if(toState.access){
if(toState.access.loginRequired){
if ($localStorage.token == null) {
$state.go('login');
}
}
}
}
Related
I have a Spring+AngularJS RestFul application with user identification capabilites.
As soon as the user is logged in I store this data in the following manner:
$window.sessionStorage.user
Because I want to have this sessionStorage in other tabs I have implemented in my main controller the solution proposed here: https://stackoverflow.com/a/32766809/1281500
Also in this controller I listen to $stateChangeStart events to check wether the user is logged in or not. If the user is not logged in, I redirect him to the corresponding login page.
My problem comes when I open a new tab in the explorer. As you'll see in the code below, I get the $window.sessionStorage.user variable from the old tab to the new tab like this:
else if (event.key == 'sessionStorage' && !$window.sessionStorage.length) {
// another tab sent data <- get it
var data = JSON.parse(event.newValue);
for (var key in data) {
$window.sessionStorage.setItem(key, data[key]);
}
But the code of the $stateChangeStart is executed BEFORE the snippet above so the $window.sessionStorage.user is not available yet and the user is always redirected to the login page even when he's already logged in (at least in the original tab)
$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
// if the user tries to access a restricted page an is not logged in, he is redirected to the login view
var restrictedPage = $.inArray(toState.name, ['login', 'sign-up']) === -1;
// "$window.sessionStorage.user" IS ALWAYS "UNDEFINED" just after a new tab is opened
if (restrictedPage && $window.sessionStorage.user === undefined) {
// Go to login page
}
});
The full controller code is below. How can I have user credentials available in the $stateChangeStart block?
(function() {
'use strict';
angular
.module('app.core')
.controller('CoreController', CoreController);
CoreController.$inject = ['$q', '$scope', '$rootScope', '$state', '$location', '$cookies', 'LoginService', '$window'];
function CoreController($q, $scope, $rootScope, $state, $location, $cookies, LoginService, $window) {
var vm = this;
var sessionStorage_transfer = function(event) {
if(!event) { event = $window.event; } // ie suq
if(!event.newValue) return; // do nothing if no value to work with
if (event.key == 'getSessionStorage') {
// another tab asked for the sessionStorage -> send it
$window.localStorage.setItem('sessionStorage', JSON.stringify(sessionStorage));
// the other tab should now have it, so we're done with it.
$window.localStorage.removeItem('sessionStorage'); // <- could do short timeout as well.
} else if (event.key == 'sessionStorage' && !$window.sessionStorage.length) {
// another tab sent data <- get it
var data = JSON.parse(event.newValue);
for (var key in data) {
$window.sessionStorage.setItem(key, data[key]);
}
}
};
// listen for changes to localStorage
if($window.addEventListener) {
$window.addEventListener("storage", sessionStorage_transfer);
} else {
$window.attachEvent("onstorage", sessionStorage_transfer);
};
// ask other tabs for session storage (this is ONLY to trigger event)
if (!$window.sessionStorage.length) {
$window.localStorage.setItem('getSessionStorage', 'user');
$window.localStorage.removeItem('getSessionStorage', 'user');
};
$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
// if the user tries to access a restricted page an is not logged in, he is redirected to the login view
var restrictedPage = $.inArray(toState.name, ['login', 'sign-up']) === -1;
if (restrictedPage && $window.sessionStorage.user === undefined) {
// Go to login page
}
});
}
})();
UPDATE
My application is stateless so it works in the following way:
The first time the user access to the application I check the username and password are correct (call to Spring Rest service) and at that point I generate a token for the user. This token and user data are both stored in the sessionStorage like $window.sessionStorage.authToken and $window.sessionStorage.authToken.
I manage this from another AngularJS Controller (LoginController):
function login(valid) {
if(!valid) return;
LoginService.authenticate(vm.user)
.then(
function(user) {
var authToken = user.token;
$window.sessionStorage.authToken = authToken;
vm.authError = false;
if (vm.rememberMe) {
var now = new Date();
var expireDate = new Date(now.getFullYear(), now.getMonth() + 1, now.getDate());
$cookies.put(AUTH_TOKEN_COOKIE, authToken, {'expires': expireDate});
}
LoginService.getUser()
.then(
function(user) {
$window.sessionStorage.user = user
$state.go('installations');
}
);
},
function(errAuthentication) {
vm.authError = true;
$window.sessionStorage.user = null;
}
);
}
From now on I send this token with every request to the RESTful application and I have a filter in Spring that checks the token with the user credentials is correct. In case this token doesn't exist or is not valid anymore, Spring Security throws the corresponding Exception and I catch this Exception to redirect to the login page (I don't think this code is important for the current question but I can post it if necessary).
So basically my user only lives in the current sessionStorage object, I don't have a service to check if the user is logged in or not, I just check the variables I stored during the first log in process.
I hope this helps to clarify the process I little bit more.
Thank you very much.
It's really difficult to guarantee that your controller will run before other modules.
If you want to execute this check before everything else you need to do that on a run module.
angular
.module('myApp')
.run(runBlock)
.run(['mySessionChecker', '$rootScope', function (mySessionChecker, $rootScope ) {
$rootScope.$on("$locationChangeStart", function () {
//Service that checks if the user is logged or not
mySessionChecker.checkSession().then( function (auth) {
//Auth tells you if the user is logged in or not
console.log('User is '+auth);
doSomething();
})
});
}]);
function runBlock($log) {
$log.log('Angular's running');
}
Run Module will bootstrap immediately after App Module.
Then, for instance, you can emit an event with $rootScope or using Postal.js and do something with that information.
I would create a Parent Controller which intercepts that event and I would extend the controllers where you need to do the check to understand if the user is logged or not.
Take a look at this Q/A.
Hope I've been helpful.
I have created an app with angular ui routing and states. Also I have a WEB API controller that can use to learn if user is authenticated or not. The problem is that http request to that WEB API has some delay to return result because is asynchronous.
I want, every time when user wants to go on state, to check if is authenticated or not, and give him access or redirect to login page. But first time when app running with someway i want to wait until i have answer from my WEB API.
I have this part of code
PlatformWeb.run(function ($rootScope, $state, $timeout, Authorization) {
$rootScope.$state = $state;
$rootScope.User = {
isAuthenticated: null,
targetState: null
}
Authorization.isAuthenticated().then(function (result) {
$rootScope.User.isAuthenticated = result;
$state.go($rootScope.User.targetState.name);
})
hasAccess = function () {
return $rootScope.User.isAuthenticated;
}
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
$rootScope.User.targetState = toState;
if ($rootScope.User.isAuthenticated == null)
event.preventDefault();
else {
if (!hasAccess()) {
event.preventDefault();
$state.go('PlatformWeb.Login');
}
}
});});
The first time when app runs, $rootScope.User.isAuthenticated is null, so i will prevent to load state. Also with my 'Authorization' service i call my asynchronous function to get if user if authenticated or not. Before i prevent loading state i keep when user wants to go, so when i have my result back from WEP API, I change the state '$state.go($rootScope.User.targetState.name);' to the state he want. Then I know if is authenticated or not and i ask if has permissions to go. If he hasn't then i redirect him to login state.
BUT event.preventDefault(); doesn't work as expected. When i run my app i get this error 'angular.min.js:6 Uncaught Error: [$rootScope:infdig] http://errors.angularjs.org/1.3.7/$rootScope/infdig?p0=10&p1=%5B%5D' multiple times.
Angular documentation says :
'10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: []'
In my logic, 'stateChangeStart' function in condition '$rootScope.User.isAuthenticated == null' with event.preventDefault(); will make app logs here, and not run more until i know. When i get my result back from my WEBAPI I will go again in this function, but this time i know in which state to send him.
I would suggest, firstly always check where is current change navigating to... and get out if it is already redirected
$rootScope.$on('$stateChangeStart',
function (event, toState, toParams, fromState, fromParams) {
// are we already going to redirection target?
if(toState.name === 'PlatformWeb.Login'){
return; // yes, so do not execute the rest again...
}
$rootScope.User.targetState = toState;
if ($rootScope.User.isAuthenticated == null)
event.preventDefault();
else {
if (!hasAccess()) {
event.preventDefault();
// we will redirect here only if not already on that way...
$state.go('PlatformWeb.Login');
...
I am new to angularjs and started implementing login/logout for my assignments.
What i am doing now is-
$rootScope.$on('$stateChangeStart', function (event, toState, toParams,fromState) {
var token = SessionService.get('token')
LoginService.isLoggedIn({token:token}).$promise.then(function(response){
if(!response.error){
var isLoggedInOnServer = response.object
var requireLogin = toState.data.requireLogin
if(!isLoggedInOnServer && requireLogin){
console.log("1....")
event.preventDefault()
$state.go('user.signin');
}
console.log(toState.data.title,['signin','signup','forget'].indexOf(toState.data.title),isLoggedInOnServer,requireLogin)
if(isLoggedInOnServer && !requireLogin && ['signin','signup','forget'].indexOf(toState.data.title) > -1){
console.log("2....")
$state.go('app.dashboard')
}
}else{
console.log("3....")
event.preventDefault();
$state.go('user.signin')
}
})
The problem with this is when i hit a secure page, it first got there and show that page in flicker then return to login page if i am not login.
If i am not login this should immediately redirect me to login page instead of that secure page.
Like Spring-security in spring, can any body tell me robust login mechanism or some logic that i can implement?
I'm making a login with redirection too.. this is working fine to me.
when i login, the token is stored on a cookie and a variable to render my menubar.
if my cookie is empty and my render too this redirect to the index.html (my login)
var miApp = angular.module('Natura', ['ngRoute', 'ngTable', 'ngCookies'])
.run(function ($rootScope, $location, $cookies, $window, loginService) {
$rootScope.render = $cookies.get('render');
$rootScope.$on('$routeChangeStart', function () {
if ($window.location.pathname !== "/NaturaWEB/index.html") {
if ($rootScope.render !== "true") {
$window.location.href = 'index.html#/';
}
}
});
});
with $window.location.pathname i can control what url is being trying to access.
sorry if this is a lot crappy.
I have a problem with my authentication mechanism. I have to call an API to get current user roles, then check it to decide that user can view which pages of my app. My idea: call API in myApp.run block and then check:
angular.module('MyApp')
.run(['$rootScope', 'authenService', '$location', function($rootScope, authenService, $location) {
var currentUser;
function checkRole() {
if(angular.isDefined(currentUser) && angular.isObject(currentUser) && ... && currentUser.isAdmin && $location.url() === '/dashboard') {
return true;
}
return false;
}
/*
** #name: getCurrentUser
** #description: get current user info, use promise $q
*/
authenService.getCurrentUser()
.then(function getSuccess(currentUserInfo){
currentUser = currentUserInfo;
//if user is not admin and current page is not dashboard page, redirect to dashboard page
if(!checkRole()) {
$location.path('/dashboard');
}
}, function getError(){});
//when user go to new path, check role if user have permission to access new page or not
$rootScope.$on('$routeChangeStart', function onRouteChange() {
if(!checkRole()) {
$location.path('/dashboard');
}
})
}];
My problem: while getCurrentUser API is in progress (still not receive response from server), the code $rootScope.$on('$routeChangeStart') is executed and always redirects user to dashboard page.
What should I do? Do you have any idea/solution to resolve this problem? or How can I wait for response from server and then run the UserCtrl controller(when user go to /user, the UserCtrl will execute without checkRole because response hasn't been received).
EDIT
The things I really want to do are when user goes to mySite.com, I will call an API to get user roles. The request is still in progress, but somehow user goes to mySite.com/#/user, the User page shows in web immediately(The requirement is user can't see User page till the request finishes). After the request finished, user will be redirected to mySite.com/#/dashboard if user doesn't have permission.
SOLUTION
I show loading screen to block my app till the request finished. Thank you for your help.
try using $q service:
angular.module('MyApp')
.run(['$rootScope', 'authenService', '$location', '$q', function($rootScope, authenService, $location, $q) {
var currentUser;
function checkRole() {
var defered = $q.defer();
if(angular.isDefined(currentUser) && angular.isObject(currentUser)){
defered.resolve(currentUser.isAdmin && ($location.url() === '/dashboard'))
}else{
authenService.getCurrentUser().then(function getSuccess(currentUserInfo){
currentUser = currentUserInfo;
defered.resolve(currentUser.isAdmin && ($location.url() === '/dashboard'))
});
}
return defered.promise;
}
$rootScope.$on('$routeChangeStart', function onRouteChange() {
checkRole().then(function(isAdmin){
if(isAdmin) $location.path('/dashboard');
})
})
}];
I'm trying to introduce login into the way the user navigates accross the application.
I pretend to redirect the user to the page were he was before he navigate to the login page if that page meets specific requirements
Preventing the event from the $stateChangeStart stop's the state change like expected but when i run the $state.go('into_somewhere') i enter an infinit loop
My angular version is 1.3.1 and the ui-router is the latest
.factory('RouteHistory', function ($rootScope,$log, $state, Auth, $urlRouter, $timeout) {
// after the user enter a page
var currentState = '';
// when the user is trying to access a page that he has not permissions
// or that requires the user to be logged in
var pendingState = '';
var isMenuTogglerVisible = false;
var skipFromStateVal = true;
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){
event.preventDefault();
if (toState.name == 'login' && fromState.name != 'login'){
$log.log('Ui-router: changing to login');
// $urlRouter.sync();
$state.go('login')
//pendingState = fromState;
//$log.log('Peding state updated to:' + pendingState.name );
//$urlRouter.sync();
}
if (fromState.name == 'login' && Auth.isLoggedIn()) {
$log.log('Ui-router: going from login');
//$state.go(fromState.name);
$timeout(function(){
// $state.go('home', null, {/*reload: true, location: 'replace'*/});
$state.go('browse-machine');
//$urlRouter.sync();
},2000)
}
$log.log({
'toState': toState,
'toParams': toParams,
'fromState': fromState,
'fromParams': fromParams
})
})
return {
};
});
In general I would say, let's redirect ($state.go()) only if needed. In other cases, get out from the event listener:
if (toState.name === 'login' ){
// doe she/he try to go to login? - let him/her go
return;
}
if(Auth.isLoggedIn()){
// is logged in? - can go anyhwere
return;
}
// else
$state.go('login')
This is simplified logic, but shows, that we should change to execution only if needed. There are some other examles with more detailed implementation and plunkers:
Confusing $locationChangeSuccess and $stateChangeStart
Angular UI Router: nested states for home to differentiate logged in and logged out
other example of log in
angular ui-router login authentication
As provided in the comment, there was plunker, which I changed like this here
...
// three new lines
if (toState.name === 'specialRoute'){
return;
}
if (fromState.name=='route1'){
event.preventDefault();
$state.go('specialRoute')
}
And this is not looping anymore. Please, check it here
You should use the notify option :
$state.go('your.state',{ your params },{notify: false});
This will prevent stateChangeStart to fire again.
This answer helped me:
$urlRouterProvider.otherwise( function($injector, $location) {
var $state = $injector.get("$state");
$state.go("app.home");
});
Original:
Why does AngularJS with ui-router keep firing the $stateChangeStart event?
I simply used $location.path('every/where') instead of $state.go('every/where')
:) .
The infinite loop is partly caused by
if (toState.name == 'login' ...) { $state.go('login'); ...
..which says if you're going to the login state, then go to the login state.
...And calling event.preventDefault() as the first line in the event handler doesn't help. When you use go() to go to the login screen (or anywhere else), that state change is also prevented by event.preventDefault(). It should only be used within an if.
Your entire $stateChangeStart handler should instead be...
if (!Auth.isLoggedIn() && toState.name != 'login') {
event.preventDefault();
Auth.desiredState = toState.name;
$state.go('login');
}
...which reads naturally. "If you're not logged in and you're not already going to the login screen, then stop what you're doing, I'll remember where you wanted to go, and you now go to the login screen."
Later your Auth object will issue a $state.go(Auth.desiredState) when it's satisfied with the user.
It works for me, Below code helps to get rid of infinite loop
let firstPass = true;
$scope.$on('$stateChangeStart', function(event, toState, toParams) {
if ($scope.addForm.$dirty && firstPass) {
event.preventDefault();
ConfirmationDialog.openNavigateAwayConfirmationModal().then(function () {
firstPass = false;
return $state.go(toState, toParams);
});
firstPass = true;
}
});