Below i have a state in my routes.js in my angular routes controller. I want it to check if the user is an admin, and only if it is an admin it goes to admin, otherwise it is redirected to home page.
There are no errors on the console if the user is logged in and is an admin, also it redirects if the user is not an admin and logged in, but if a user is not logged in and not an admin i can still see the template of that page and it says POST http://localhost:3000/users/sign_in.json 401 (Unauthorized)
.state('adminpage', {
url: '/adminpage',
templateUrl: 'views/adminpage.html',
controller: 'AdminPageCtrl',
onEnter: function(Auth, $state){
Auth.currentUser().then(function( user){
if (user.isadmin) {
$state.go('adminpage');
}
else {
$state.go('home');
}
})
}
})
I assume Auth.currentUser() rejects the promise if user is not logged in. So, you need to define what should be done on promise rejection, i.e. if user is not logged in.
onEnter: function(Auth, $state){
Auth.currentUser().then(function( user){
if (user.isadmin) {
$state.go('adminpage');
}
else {
$state.go('home');
}
})
.catch(function() {
// Redirect the user somewhere else, or show an error here.
});
}
The .catch() method actually handles the rejection of the promise, and you can try to redirect the user somewhere else in this block. In this way, the restricted page won't be shown to the user.
Hope that helps.
Related
I am working on an angular app. It's an SPA. I am loading the profile(or myAccount) page into the home page when user clicks on a link.
<a href="#" ng-click="getProfileData()"/></a>
in my controller:
$scope.getProfileData(){
$http.get('/profile').then(
function success(){
},
function error(){
}
}
}
the link makes an ajax request through $http service of angular.
what I want to do is, when user clicks on the link, before making the ajax request to check if he's logged in or not. if not, open the sign in popup, and after sign in continue with the same request.
I've looked into $httpProvider.interceptors, but I'm still not sure how to do it, since I need to pause hold the $http request till the login is completed, modify the config by adding login details into it and then continue.
Is using of interceptors the correct option? If yes, how should I proceed towards my objective?
EDIT:
my primary concern is to continue with the same request. i.e. i don't want the user to click on profile link again after he has logged in. i can check if user is logged in or not before making the ajax request. if he is, then it's good. but if he's not, he has to log in (in the modal i pop up), and then he has to click on the profile link again. is there a way to continue the previous http request after login?
There are only three points in the application where the login modal should appear:
When you are on a welcome page and you click “Login”.
When you are not logged in and you attempt to visit a page that requires login.
When you attempt to make a request that requires a login(Ex:session expiration).
Determining which pages require a logged in user
Considering you are using ui router,you can secure routes of your application with the help of attaching additional properties to a route.Here we add a requireLogin property for each state.
app.config(function ($stateProvider) {
$stateProvider
.state('welcome', {
url: '/welcome',
// ...
data: {
requireLogin: false
}
})
.state('app', {
abstract: true,
// ...
data: {
requireLogin: true // this property will apply to all children of 'app'
}
})
});
For routes that do not require a login we set requireLogin to false.
Capturing attempted state changes
This is where we are going to capture the attempted state changes and inspect them for our requireLogin property.
app.run(function ($rootScope) {
$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
var requireLogin = toState.data.requireLogin;
if (requireLogin && typeof $rootScope.currentUser === 'undefined') {
event.preventDefault();
$rootScope.returnToState = toState.url;
// get me a login modal!
}
});
});
As you can see, if the route requires a login and $rootScope.currentUser is not yet set, we will prevent the attempted state change and show the modal.
Redirecting to initially requested url after login
The last part requires redirecting to the initially requested url after login.In our app.run we have set a variable $rootScope.returnToState = toState.url which we can use in our login controller to redirect.In our controller for the login page we can do something like this:
$scope.login = function(form) {
$scope.submitted = true;
if(form.$valid) {
Auth.login({
email: $scope.user.email,
password: $scope.user.password
})
.then( function() {
// Logged in, redirect to correct room
if( $rootScope.returnToState) {
$location.path($rootScope.returnToState);
} else {
//redirect all others after login to /rooms
$location.path('/home');
}
})
.catch( function(err) {
$scope.errors.other = err.message;
});
}
};
For further reference please refer to this blog post http://brewhouse.io/blog/2014/12/09/authentication-made-simple-in-single-page-angularjs-applications.html
This can help you build a rock solid authorization for your app which I think you might be looking for.
Why can't you just make another function call to verify, if the user is logged-in. And based on that fire up the ajax request that you are trying up there. Something like
$scope.getProfileData(){
if($scope.isLoggedin()){
$http.get('/profile').then(
function success(){
},
function error(){
}
}
}
};
$scope.isLoggedin = function(){
// Do some processing and return true/false based on if user is logged-in
};
I am using AngularJS with UI-Router and Meteor and want to redirect users to their intended destination after successfully logging in. I have a Meteor Session variable storing the intended page. After signing in, which I currently have implemented according to my previous SO post reproduced below, how do I redirect the user to the intended login page?
app.run(function($rootScope, $state, UserService) {
$rootScope.$on('$stateChangeStart', function(event, toState) {
// don't check auth on login routes
if (toState.name === "signin") {
if (UserService.doesNotHaveVerifiedEmail()) {
event.preventDefault();
Session.set("INTENDED_STATE_NAME", toState.name);
$state.go('signin');
return;
}
}
}
});
I'm implementing some simple client-side authentication logic in Angular.js. The pages involved are:
/account#/login (public)
/account (require login)
/account#/settings (require login)
When a user is not logged in and try to visit either /account or /account/#/settings, the app is supposed to redirect to the login page.
I have the following routes configured using ui-router:
$stateProvider
.state('overview', {
url: '/',
restricted: true
})
.state('settings', {
url: '/settings',
restricted: true
})
.state('login', {
url: '/login',
restricted: false
})
and upon URL change, I check if the upcoming page is a restricted page and whether the current user is not logged in. If so redirect to login.
app.run(function($rootScope, $location, $state, auth) {
$rootScope.$on('$stateChangeStart', function(event, next) {
if (next.restricted && !auth.isLoggedIn()) {
event.preventDefault();
$state.go('login');
}
});
});
auth is just a service that checks the login status and returns either true (logged in) or false (not logged in).
Here's my question:
Even though this (kind of) works, I see a page flickering issue when trying to visit a restricted page while not logged in. The page flashes the contents of the restricted page quickly before redirecting me to the login page.
I did a little bit researching online and some people have mentioned the potential solution could be using resolve when defining my states, since the page won't load unless it resolves successfully. However, when I try to add
resolve: {
load: function(auth) {
return auth.isLoggedIn();
}
}
It didn't work. What am I missing? Is using resolve the way to go?
The way you are currently doing it will check if the user is logged in or not and set load to true or false. Also controller gets instantiated before load is resolved which is why you see the flickering. You need to achieve two things here:
Make sure that load is resolved before the controller is instantiated.
If user is not logged in, redirect the user to the login page.
For the first part we need to use a promise as it will be resolved and converted to value before controller is instantiated. This is what the documentation says:
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.
Following code can do that for us:
var isLoggedin = ['auth', '$q',
function(auth, $q) {
var deferred = $q.defer();
//assuming auth.isLoggedIn returns a promise
var loginPromise = auth.isLoggedIn();
loginPromise.then(
function(response) {
deferred.resolve(response);
},
function(error) {
deferred.reject('Not logged in');
});
return deferred.promise;
}
];
And states will use isLoggedin:
$stateProvider
.state('overview', {
url: '/',
resolve: {
loggedin: isLoggedin
}
})
.state('settings', {
url: '/settings',
resolve: {
loggedin: isLoggedin
}
})
.state('login', {
url: '/login'
})
For the second problem, that is redirecting the user to login page, you can listen to $stateChangeError event which is fired in case the state is not resolved, and use $state.go to redirect the user.
My AngularJS app has a $http intercept that checks for 401's returned from any api calls and redirects to the login state. This is working everywhere except on the following:
I have one state which has a resolve. Basically, before going to the User Details page, I want to load the user first:
.state('main.userdetails', {
url: 'users/{uid}',
templateUrl: 'views/user-details.html',
controller: 'UserDetailsCtrl',
controllerAs: 'ud',
resolve: {
user: ['userService', '$stateParams', function(userService, $stateParams) {
console.log('-- Resolver User called---' + $stateParams.uid)
return userService.getOne($stateParams.uid);
}]
}})
If I log in to the site, then delete my token from LocalStorage, when I browse to any $state that makes an API call, I'm redirected to the login page. However, when I browse to the above state, I see the User Details page with blank info. In the console, I can see that the interceptor was called but I am not redirected.
Inside userService.getOne():
userFactory.getOne = function(id) {
user = {};
return $http.get(config.apiURL() + '/users/' + id)
.then(function(res) {
user = res.data;
return user;
}, function(error) {
console.log('Error GETing in userservice');
return error;
})};
And my interceptor's responseError:
responseError: function(rejection) {
if (rejection.status === 401) {
console.log('--Auth Interceptor: responseError---');
authToken.removeToken();
$location.path('/login');
console.log('---Auth Interceptor: sent to /login');
}
return $q.reject(rejection);}
The console shows me this:
--- Resolver User called---user1#test.com
GET http://test.com:3030/api/users/user1#test.com 401 (Unauthorized)
---Auth Interceptor: responseError---
---Auth Interceptor: sent to /login
Error GETing in userservice
---UserDetailsCtrl Entered---
Clearly everything's being executed in the right order, but the $location.path('/login') isn't doing anything.
What am I doing wrong?
However, when I browse to the above state, I see the User Details page with blank info.
That's because your error handler
function(error) {
console.log('Error GETing in userservice');
return error;
}
returns error, which is in fact the rejection reason, that is, a plain string, and passes that string as the value of user to your controller. Since you're returning a valid string from your error handler, it assumes that the error was treated. To avoid this assumption, you can either, from the error handler, throw an error, or return a rejected promise (again).
In the home state, I have a function that checks if the current user is authorized along with other promises.
resolve: {
authorize: ['AuthService', function (AuthService) {
console.log("authorize from home");
return AuthService.authorize();
}],
competitions: ['Competition', function (Competition) {
return Competition.query();
}]
}
The AuthService.authorize activates the login state if there is no user logged in.
var authorize = function () {
console.log("Authorize ");
if (!isLoggedIn()) {
$q.when(angular.noop).then(function () {
console.log("Not logged in, go to login state");
$state.go('login');
});
}
};
It's working as expected the login state is activated but the competitions promise is resolved (I can see the REST call returning a 401 if no user logged in which is correct). Since the authorize resolve activates another state, is there a way to prevent the following resolve to be executed?
My implementation is based on that question: angular ui-router login authentication