This question already has answers here:
AngularJS ui-router login authentication
(10 answers)
Closed 7 years ago.
I put in place a simple login system in my angular application similar to the one at http://demos.angularcode.com/angularcode-authentication-app/#/login
The problem that I'm facing now is that if I try to access a page when I'm not logged in, it first displays the restricted page for a second before redirecting to login.
You can see this behaviour if you try to go directly to the page http://demos.angularcode.com/angularcode-authentication-app/#/dashboard
The earliest I can do the authentification check is in my module declaration:
angular.module('myApp', [])
.run(function() {
//check auth + redirect
}
Is there a way to not display the restricted page first before redirecting to login?
EDIT
I use $urlRouterProvider and $stateProvider to manage routes.
For example the home page:
app.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/home");
$stateProvider
.state('home', {
url : '/home',
templateUrl : 'views/home.html'
})
You can 'catch' the state change start event, and check if the user may or may not be redirected to the requested page:
app.run(function ($rootScope, $state) {
$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
// run logic to check if login is required
if (requireLogin && !userIsLoggedIn) {
event.preventDefault(); // stop redirecting
$state.go("login");
} else {
// allowed to go to requested state
// .. do nothing, user will go to state
}
});
});
Bind a variable to rootScope in order to check if user is logged in or not like $rootScope.isLoggedIn, and set it false by default.
Then add
ng-show="isLoggedIn"
attribute to home.html container.
I think it will fix your problem.
angular.module('myApp', [])
.run(function() {
//Firstly Display Restricted page then after some time show the login page
}
Use this system $timeout. Show your restricted page and then call the login page from the same function after whatever time gap you want.
Check the documentation here for implementation. https://docs.angularjs.org/api/ng/service/$timeout
Check this Example - Pic is changed after some time
http://jsfiddle.net/thomporter/vqVrX/
You can check other jsfiddles as well to understand the concept.
Related
I have a specific logic sequence in my app, and I want a simple way to force my app to start from the welcome page.
I am using this:
$urlRouterProvider.otherwise('/pages/welcome');
the problem is that otherwise just play with the unknown URLs and redirect them to the welcome, whereas I want to redirect to the welcome in all cases, even in the registered states.
Simply try location.hash = '#/'; like the following:
angular.module('app', []).config(function ($stateProvider, $urlRouterProvider) {
location.hash = '#/';
$stateProvider
.state('welcome', {
url : '/pages/welcome',
templateUrl: 'views/welcome.html',
controller : 'WelcomeCtrl'
});
// if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/pages/welcome');
})
i think you are redirecting to page not any state. You need to mredirect to state.
$urlRouterProvider.otherwise("/state1");
I am developping an OAuth Provider application, using AngularJS and ui-router.
For each state change, I do the following check:
If the user is already logged in:
1.1 If the user is not an admin -> redirect him to the callBackUrl
1.2 If the user is an admin, do nothing
If the user is not logged in:
2.1 If the user tries to access an admin page -> redirect him back to login
2.2 If not, do nothing
my ui-router run method is the following:
.run(function ($rootScope, $state, $auth, accountService, $window, $stateParams) {
$rootScope.$on('$stateChangeStart', function (e, toState, toParams, fromState, fromParams) {
accountService.getUser({ token: $auth.getToken() }).$promise
.then(function (response) {
if (response.isAdmin === false) {
e.preventDefault();
$window.location.href = $stateParams.callBackUrl;
return;
}
})
.catch(function (response) {
if (toState.name.split(".")[0] === 'admin') {
e.preventDefault();
$state.go('root.login');
}
});
});
});
Everything is OK except for the part where I redirectthe user to the callback URL using $window
$window.location.href = $stateParams.callBackUrl;
This redirection takes 2-3 seconds, and in the meantime my user can see the page he is trying to access on my application. I thought the use of preventDefault() would solve that but it doesn't. Do you know how I could hold the $statechangeevent so that the user is redirected directly to the callback URL?
Thanks
I would put it this way:
The above approach allows user everything, until he is not really proven to be UN-authenticated. Why that? Because the code is calling service and evaluating all the stuff once the data are received. Meanwhile - we trust the user.
So, I'd suggest to change the approach
NEVER trust the user. He/she must to do the best to prove he/she is the right one, to get somewhere ... (well, kind of that...)
I described one possible way (with working example) here:
Confusing $locationChangeSuccess and $stateChangeStart
Just a piece of code to cite
Th first part of the $rootScope.$on('$stateChangeStart', ...:
// if already authenticated...
var isAuthenticated = userService.isAuthenticated();
// any public action is allowed
var isPublicAction = angular.isObject(toState.data)
&& toState.data.isPublic === true;
// here - user has already proved that he is the one
// or the target is public (e.g. login page)
// let him go, get out of this check
if (isPublicAction || isAuthenticated) {
return;
}
The second part, user is not trusted, he requires access to private stuff
// now - stop everything
// NO navigation
// we have to be sure who user is, to continue
// 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");
})
Check that, in action, here
I know this was asked a while ago, but here is a possible solution:
Ui-router actually provides a fantastic way to solve this. By using a "resolve" within your $stateProvider, you can check that the user is authenticated before the controller of that particular state is even instantiated. Here is what ui-router docs say about resolve:
Resolve
You can use resolve to provide your controller with content or data that is custom to the state. resolve is an optional map of dependencies which should be injected into the controller.
If any of these dependencies are promises, they will be resolved and converted to a value before the controller is instantiated and the $stateChangeSuccess event is fired.
https://github.com/angular-ui/ui-router/wiki - Section for resolve is almost half way down page
You can run accountService.getUser within the resolve to check for an authenticated user, and by doing so, would prevent someone who is not authed from seeing the view they are trying to route to.
The resolve is set up inside the $stateProvider, and may look something like this:
$stateProvider.state('myState', {
resolve: {
userAuth: function(accountService) {
return accountService.getUser();
}
}
)
If you notice in the above example, I set a property called userAuth within the resolve. This can now be injected into any controller or service and then you can check against it for authenticated users. Each state that needs to be a "protected" view can contain the resolve, and then the 2-3 second flash of the view won't occur, as the controller hasn't been instantiated, and the user is redirected to another state.
Let's say I have 4 routes - 2 require the user to be logged in, 2 do not. My app init looks like:
$routeProvider.when('/open1',{templateUrl:'/open1.html',controller:'Open1'});
$routeProvider.when('/open2',{templateUrl:'/open2.html',controller:'Open2'});
$routeProvider.when('/secure1',{templateUrl:'/secure1.html',controller:'Secure1'});
$routeProvider.when('/secure2',{templateUrl:'/secure2.html',controller:'Secure2'});
Routes /open1 and /open2 are open to all, while routes /secure1 and /secure2 require the user to be logged in and, if not, take some action, e.g. redirect to login or launch a warning. I can determine the user's state by using my Auth service and calling, e.g., Auth.isLogin(). So the result would be:
going to /open1 and /open2 always go to the template and controller declared above
if Auth.isLogin() returns true, /secure1 and /secure2 go to the above-declared template and controller
if Auth.isLogin() returns false, /secure1 and /secure2 take some other action, e.g. $location.path('/login')
I could put logic in the Secure1 and Secure2 controllers that checks, but that is repetitive and mixes up responsibilities, makes them harder to test, etc.
Is there a way that I can use the $routeProvider to declare, "check this route and this route and if not, redirect"? I was thinking of using resolve somehow, but not quite sure how to work it in (docs on resolve are not very clear, and few helpful examples).
EDIT:
based on the answers below, it appears there are three philosophies for doing this:
Using resolve to check logged in and fail the promise, and then catching the $routeChangeError event to redirect http://www.sitepoint.com/implementing-authentication-angular-applications/
Using just $routeChangeStart event to check logged in and redirect http://arthur.gonigberg.com/2013/06/29/angularjs-role-based-auth/
Using just resolve to check logged in and redirect http://midgetontoes.com/blog/2014/08/31/angularjs-check-user-login
The 2nd option is what the two answerers have suggested.
As in my comments above, there are 3 different paths (plus the ability to use a directive if you want to control it from within html templates). I ended up following
https://midgetontoes.com/angularjs-check-user-login/
which essentially is as follows:
$routeProvider.when('/secure', {
templateUrl: '/secure.html',
controller: 'Secure',
resolve:{
loggedIn:onlyLoggedIn
}
});
And then onlyLoggedIn:
var onlyLoggedIn = function ($location,$q,Auth) {
var deferred = $q.defer();
if (Auth.isLogin()) {
deferred.resolve();
} else {
deferred.reject();
$location.url('/login');
}
return deferred.promise;
};
Simple, works like a charm. If I ever need a directive, I will pull this piece into a service.
This blog post deals with user authentication in AngularJS using directives.
The $route service emits $routeChangeStart before a route change.
If you don't use directives, you can catch that event by calling app.run (you can place it after the code where you define the routes [app.config]). For example:
For full disclosure I use ui.router and this is an adapted code from $stateChangeStart I use in my app
var app = angular.module('app');
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/open1',{templateUrl:'/open1.html',controller:'Open1'});
$routeProvider.when('/open2',{templateUrl:'/open2.html',controller:'Open2'});
$routeProvider.when('/secure1',{templateUrl:'/secure1.html',controller:'Secure1'});
$routeProvider.when('/secure2',{templateUrl:'/secure2.html',controller:'Secure2'});
}]);
app.run(['$rootScope', '$location', 'Auth', function($rootScope, $location, Auth) {
$rootScope.$on('$routeChangeStart', function(event, currRoute, prevRoute){
var logged = Auth.isLogin();
//check if the user is going to the login page
// i use ui.route so not exactly sure about this one but you get the picture
var appTo = currRoute.path.indexOf('/secure') !== -1;
if(appTo && !logged) {
event.preventDefault();
$location.path('/login');
}
});
}]);
I had the same problem and I did it this way:
var app = angular.module('myModule',["ui-bootstrap"]);
And then listen for a locationchange in the app (this will also trigger onEnter of a page)
app.run(function ($rootScope, $location, $cookieStore) {
$rootScope.$on("$locationChangeStart", function (event, next, current) {
//Here you can check whatever you want (servercall, cookie...)
});
}
I Hope this helps!
I'm trying to redirect unauthorized people when they first enter my website via a url;
eg: example.com/#/order should be redirected to example.com/#/auth, this includes when they first visit the webpage and also navigating inbetween states.
Currently I have an abstract parent state of /order and /auth which have resolves that check for authentication and redirect otherwise. I also have a watch on the $stateChangeStart event to do the same thing.
The code for when you initially load the page works correctly, it will redirect if you visit /order/restaurant without being logged in, however if I'm on the url /auth/login I can change my url to /order/resturant and it will redirect me successful but the view will not update. I will still be able to see the /order/resturant page but the resolve and page changes were hit. Why does this happen? I've attempted to use $rootScope.$apply() without success as well.
My code is as follows for the parent states:
// Authentication Urls
.state('auth', {
url: '/auth',
templateUrl: 'modules/auth/auth.html',
abstract: true
})
// Order Urls
.state('order', {
url: '/order',
templateUrl: 'modules/order/order.html',
abstract: true
})
and my code to watch the stateChange
.run(['$rootScope', '$location', 'Auth', function($rootScope, $state, Auth) {
$rootScope.$on('$stateChangeStart', function(event, toState) {
var stateName = toState.name
console.log('State start')
if (!stateName.match(/auth/) && !Auth.isLoggedIn) {
console.log('User is not visiting auth and isn\'t logged in, redirecting....')
$state.go('auth.login')
} else if (Auth.isLoggedIn && stateName.match(/auth/)) {
console.log('User is logged in and is on the auth page, redirecting....')
$state.go('order.resturant')
}
})
}])
Looking at the documentation here (http://github.com/angular-ui/ui-router/wiki#state-change-events) you should cancel the navigation by calling event.preventDefault() before performing your new transition.
I use angularjs with ui-router library. Lets say I have some routes for admin and some routes for user. If admin or user is logged in I want to show some page for them (admin.html for admin and user.html for user, for example), otherwise login.html
On the backend I have a special url, like /auth/status/, which gives me information about the user (if he's logged and which role he has)
There are some situations I can't figure out how to handle:
I go to '/' url. The application loads. I have a run method for my app module. But how can I check if the user is logged in, when it happens asynchronously? Well, I have this and it works somehow, but I'm not sure if this is a good solution:
app.config(['$stateProvider', '$routeProvider',
function($stateProvider, $routeProvider) {
$stateProvider
.state('admin', {
abstract: true,
url: '/',
templateUrl: 'templates/admin.html'
})
.state('admin.desktop', {
url: 'desktop',
templateUrl: 'templates/desktop.html'
});
}]);
app.run([
'$http',
'$rootScope',
'$location',
'$state',
'userRoles',
function($http, $rootScope, $location, $state, userRoles) {
var DEFAULT_ADMIN_STATE = 'admin.desktop';
var promise = $http.get('/auth/status/');
promise.then(function(response) {
$rootScope.isLogged = response.data.logged;
$rootScope.userRole = userRoles[response.data.role];
if (!$rootScope.isLogged) {
$state.transitionTo('login');
} else {
switch (response.data.role) {
case 'admin': $state.transitionTo(DEFAULT_ADMIN_STATE); break;
}
}
}, function(response) {
$location.path('/login');
});
}]);
Though I don't understand: if I go to / url I should get an error because it's abstract. Instead when $http.get request is resolved (I put 2 seconds sleep in backend to check that) I transition to admin.desktop state. I'm confused what happens in which order: state loads template or app.run function with some ajax requests...
The main question is, when I go to /#/desktop how can I first check if user is logged (send a request to /admin/auth/ and check what it returns) and only then decide what to do (transition to login or desktop state)?
I found Delaying AngularJS route change until model loaded to prevent flicker this, but again still a little fuzzy for me. Resolve property seems like a solution when I want to load a list of entities and then show the template. But I want to have some "before" function for ALL states which just checks if user is logged and has a correspond role (one moment: I do not want to use /admin/entites or /user/entities urls, want to have just /entitites. As I get it several states may have the same url). So basically it looks like if I go to /someurl I want to run method wait until it gets ajax response and after that transition to some state. Instead the state corresponding to /someurl load a template...
Also I found an article about authentication in angular but author uses cookies which is not async thing
Update: when I use cookies for checking if user is logged and I go to /#/desktop I still have it rendered, and $state.transitionTo doesn't work..
You should check it before page load:
I cannot write full example now, but in common you should do like this:
.run([ ..., function(....) {
$scope.$on('$routeChangeStart', function(next, current) {
... check cookie here ...
});
}]);