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 ...
});
}]);
Related
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.
I'm having a bit of trouble here.
I have an AngularJS application that connects to an API and allows users to login, register, etc. The user receives a token that is then stored to $localStorage (via ngstorage).
$localStorage[JWT] = data.token;
When a user visits my application with a token stored, I want to present him with a loading page (regardless of which page he is trying to view) until I verify his token is correct, and then load views/controllers as usual.
I've looked into different ways of doing this, but I couldn't figure out any. Any pointers are appreciated!
Thanks.
You can use the default ngRoute module for the simple cases such as this.
First you configure the route with a default resolve properties. Here we are saying that the resolve should contain a list of movies.
app.config(function($routeProvider){
$routeProvider.when('/',{
templateUrl:'initial.html',
controller:'MainCtrl'
})
.when('/two',{
templateUrl: 'post.html',
controller: 'PostCtrl',
resolve: {
movieList: function($movies) {
return $movies.get();
}
}
})
.otherwise({
redirectTo:'/'
});
});
Secondly, we are interested into hooking into critical events of the angular system. Hence for this small example app we want to disable the button used to trigger the page change, as well as show the text loading... when the page change process starts...
(app.js):
app.controller('MainCtrl', function($scope, $location) {
$scope.go = function() {
$location.path('/two');
};
$scope.$on('$routeChangeStart', function(evt){
$scope.showLoading = true;
});
$scope.showLoading = false;
});
(initial.html):
Go
<br>
<p ng-if="showLoading" style="color:red">Loading...</p>
Entire code here
More info about ngRoute
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!
In my angular app, when a user visits the "home" state (I am using ui-router), he gets a welcome message.
When pressing a login button, a overlay appears and the user can authenticate.
Upon a successful authentication, I want to do some API calls and display stuff on the template.
All of this in code, looks like this:
.config(['$stateProvider', function($stateProvider) {
$stateProvider.state('home', {
url: "/",
templateUrl: "app/sections/home/homeTpl.html",
controller: 'HomeCtrl'
});
}])
.controller('HomeCtrl', ['$scope', 'Session', 'MoviesService', function ($scope, Session, MoviesService) {
if (Session.getUserRole() === 'USER') {
// Add a movie.
$scope.newMovie = {
name: 'I am the new movie'
};
MoviesService.save($scope.newMovie);
}
}])
When the users authenticates, the controller check's the user role and if he is a "USER" do stuff....
Now, in order for everything to work, I need Angular ui-router to refresh the state, after logging in:
$state.transitionTo($state.current, {}, {
reload: true,
inherit: false,
notify: true
});
While it works, I am not sure this is the way to go regarding best practices...
Any ideas?
I don't know if you really need notify but you can use $state.reload that an alias for transitionTo. $state doc
After I think your are right, I do the same for my app.
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.