I'm building an app using sails.js backend and angular in frontend. I'm trying to prevent the user from accessing the admin control page if he's not authorized. I've run into couple of answers already, but none of them seem to fully work.
At the moment in my app.js, I have
$stateProvider
.state('home', {
url: "/home",
templateUrl: "home/homeTemplate.html",
controller: 'homeController'
})
.state('adminPage', {
url: "/adminPage",
templateUrl: "adminPage/adminTemplate.html",
controller: 'adminPageController',
resolve: {
validate: function($q, $sails, $location) {
var defer = $q.defer();
$sails.get("/user/getCurrentUser")
.success(function(response) {
if (response.user.accessAdminPage) {
defer.resolve();
}
else {
defer.reject("Access blocked");
$location.path('/');
}
return defer.promise;
})
}
}
})
The current code is partially working; The problem at the moment is, that when the unauthorized user first logs in and lands on the home page, and then accesses localhost:1337/#/adminPage, he actually reaches the page. The url in the address bar changes to localhost:1337/#/home but the user isn't redirected. Now the weird part is, when accessing the home page afterwards through the navbar and trying to access the admin page again, the user IS redirected to the home page as intended (although there's an annoying 'flash' while the page is reloaded).
For other people asking, this kind of handling has worked, and I'm wondering what I may have missed and generally any reasons for why my current solution isn't working.
You are returning promise from success function, this will never work.
You should return defered.promise (promise object) from outside success function.
CODE
$stateProvider
.state('home', {
url: "/home",
templateUrl: "home/homeTemplate.html",
controller: 'homeController'
})
.state('adminPage', {
url: "/adminPage",
templateUrl: "adminPage/adminTemplate.html",
controller: 'adminPageController',
resolve: {
validate: function($q, $sails, $location) {
var defer = $q.defer();
$sails.get("/user/getCurrentUser")
.success(function(response) {
if (response.user.accessAdminPage) {
defer.resolve();
} else {
defer.reject("Access blocked");
$location.path('/');
}
});
return defer.promise;
}
}
});
Hopefully this could help you, Thanks.
With the solution given by pankajparkar, the issue is that you will have to reply the logic in each state declaration. I recommend you to check the user's authorization in the onStateChangeStart event
angular.module('myApp', ['ui.router'])
.run(function($rootScope, AuthService){
$rootScope.$on('$stateChangeStart',
function(event, next, nextParams, prev, prevParams) {
AuthService.isNotAutorized()
.then(function() {
event.preventDefault();
$state.go('defaultState');
});
});
});
Related
When the user hit F5 or refresh button I need to call a function before refreshing the page, I tried the below code and it did'nt work, please suggest me what I'm doing wrong and is there a better way to do this.
I'm using Anuglar 1.5 with ui-router.
angular.module('module.name', [
]).controller('someController',['$scope', function($scope) {
$scope.$on("$stateChangeStart", function () {
functionToCallOnPageRefresh();
});
}]);
functionToCallOnPageRefresh() is not getting called on page refresh.
you need to create a parent state like as 'secure' and inherit every state in your application with that state like as-
angular.module('moduleName').config(['$stateProvider', '$urlRouterProvider',
function ($stateProvider, $urlRouterProvider) {
$stateProvider.
state('secure', {
url: "/",
abstract: true,
templateUrl: "/path/to your/ master page",
resolve: {
factory: 'CheckRouting'
}
}).
state('dashboard', {
url: 'dashboard',
templateUrl: 'path/to your /template',
parent: 'secure',
});
}
]);
here i have mentioned
resolve: {
factory: 'CheckRouting'
}
in which CheckRouting inside resolve property, is a factory which is going to do some task (check user is login or not) on sate change or press f5.
in 'secure' state use 'resolve' property to execute a function if user press f5 or state change like as-
(function() {
'use strict';
angular.module('moduleName').factory('CheckRouting', ['$rootScope','$timeout' checkRouting]);
function checkRouting($rootScope,$timeout) {
if ( condition) { //user is loged in
return true;
} else {
$timeout(function() {
$state.go('login');
});
return false;
}
}
}());
Looking to build web app in Node.js with ability for user to log in (authentication), which has 3 non secure pages (/home, /contact, /about) and one secure page (/admin). As an aside, I've been referencing the scotch.io Mean Machine book.
The issue I'm having is that I've build everything out, and the login mechanism works in that when I log in, I get directed to /admin; however, when I go to /admin in the URL without logging in, I can still access the page. I.e. I'm not sure where to put the actual protection.
A bit below on how I've laid out my app. Hoping for as much a conceptual answer to suggest how I should be doing things, rather than necessarily only a code answer.
Services:
auth service posts to server the inputted username/password and returns either false or success (with user info and JWT token)
auth service also injects as AuthInterceptor the token (if there is one) into each HTTP header
Router:
angular.module('routerRoutes', ['ngRoute'])
.config(function($routeProvider, $locationProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/home.html',
controller: 'homeController',
controllerAs: 'home'
})
.when('/about', {
templateUrl: 'views/about.html',
controller: 'aboutController',
controllerAs: 'about'
})
.when('/contact', {
templateUrl: 'views/contact.html',
controller: 'contactController',
controllerAs: 'contact'
})
.when('/login', {
templateUrl: 'views/login.html',
controller: 'adminController',
controllerAs: 'login'
})
.when('/admin', {
templateUrl: 'views/admin/admin.html',
controller: 'adminController',
controllerAs: 'admin'
});
$locationProvider.html5Mode(true);
});
Controllers:
homeController, aboutController, contactController are generally empty for now
adminController:
.controller('adminController', function($rootScope, $location, Auth) {
var vm = this;
vm.loggedIn = Auth.isLoggedIn();
$rootScope.$on('$routeChangeStart', function() {
vm.loggedIn = Auth.isLoggedIn();
window.alert(vm.loggedIn); // this gives correct answer and works
Auth.getUser()
.success(function(data) {
vm.user = data;
});
});
vm.doLogin = function() {
vm.error = '';
Auth.login(vm.loginData.username, vm.loginData.password)
.success(function(data) {
vm.user = data.username;
if (data.success)
$location.path('/admin');
else
vm.error = data.message;
});
};
vm.doLogout = function() {
Auth.logout();
vm.user = {};
$location.path('/login');
};
});
And finally, below is my index.html (just the body):
<body class="container" ng-app="meanApp" ng-controller="adminController as admin">
<i class="fa fa-home">Home </i>
<i class="fa fa-shield">About </i>
<i class="fa fa-comment">Contact</i>
<i class="fa fa-comment">Admin</i>
<ul class="nav navbar-nav navbar-right">
<li ng-if="!admin.loggedIn">Login</li>
<li ng-if="admin.loggedIn" class="navbar-text">Hello {{ admin.user.username }}</li>
<li ng-if="admin.loggedIn">Logout</li>
</ul>
<main>
<div ng-view>
</div>
</main>
</body>
I won't paste the other html pages that get injected into since there isn't anything on them yet (the login.html has just the two input fields and submit button).
So a couple of questions:
In my index.html, when I click on /admin, it takes me to the admin page even if I'm not logged in. Where should I put the protection for that to not happen?
Any general comments on my setup and best practices that I'm not following?
Another nit:
I read that "li ng-if=" wouldn't show up in 'view source' if that branch of the decision tree wasn't hit, but it does. Was I misled or am I doing something wrong?
I took a custom property route to secure the routes in my application. Every state change taking place is listened for and inspected if it has this property. If it has this property set then it checks if user is logged in, if they are not, it routes them to the 'login' state.
I used UI-ROUTER in my current project where I have implemented this. I made a custom parameter called "data" that I used within the route.
Within a .config block to declare my opening routes:
$stateProvider
.state('login', {
url: '/login',
templateUrl: 'login/login.html',
controller: 'LoginController',
controllerAs: 'vm'
})
.state('home', {
url: '',
templateUrl: 'layout/shell.html',
controller: 'ShellController',
controllerAs: 'vm',
data: {
requireLogin: true
}
})
Then I add this to a .run on the application where I'm looking for ui-router's $stateChangeStart event and looking at my custom property ('data') on the state declaration:
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
var requireLogin = toState.hasOwnProperty('data') && toState.data.requireLogin;
if (requireLogin && !authService.isLoggedIn()) {
event.preventDefault();
authService.setDestinationState(toState.name);
$state.go('login');
}
if (toState.name !== 'login') {
authService.setDestinationState(toState.name);
}
});
In case you're wondering what the authService.setDestinationState does... it preserves the URL that the user was attempting to visit... once they successfully login it forwards them to that state automagically (see below):
function login() {
authService.authLogin(vm.credentials)
.then(loginComplete)
.catch(loginFailed);
function loginComplete(data, status, headers, config) {
vm.user = data;
$rootScope.$broadcast('authorized');
$state.go(authService.getDestinationState());
}
function loginFailed(status) {
console.log('XHR Failed for login.');
vm.user = undefined;
vm.error = 'Error: Invalid user or password. ' + status.error;
toastr.error(vm.error, {closeButton: true} );
}
}
When you define your Admin route, you can define a property called resolve. Each property within resolve is a function (it can be an injectable function). This function should return a promise, the promise's result can be injected into the controller.
For more information on resolve, look at http://odetocode.com/blogs/scott/archive/2014/05/20/using-resolve-in-angularjs-routes.aspx.
You can use resolve as follows to do an authentication check.
var authenticateRoute = ['$q', '$http' , function ($q, $http) {
var deferred = $q.defer();
$http.get("http://api.domain.com/whoami")
.then(function(response) {
if (response.data.userId) deferred.resolve(response.data.userId);
else window.location.href = "#/Login"
});
return deferred.promise();
}]
// ...
.when('/admin', {
templateUrl: 'views/admin/admin.html',
controller: 'adminController',
controllerAs: 'admin',
resolve: {
authenticatedAs: authenticateRoute
}
});
With this you could pass the authenticated User Id through - even if null - and let the controller deal with it, if for instance, you want a contextual message.
Else, you could do as above and only do so if there is a user Id from the authentication request, otherwise redirect to your login route.
Hope this helps! /AJ
I have simple angular ui router app:
.config(function($stateProvider, $urlRouterProvider){
$urlRouterProvider.otherwise('');
$stateProvider
.state('welcome', {
url: '/',
templateUrl: 'components/welcome/welcome.html',
controller: 'MainController',
data: {
auth: true
}
})
.state('courses', {
url: '/courses',
templateUrl: 'components/courses/courses.html',
controller: 'MainController',
data: {
auth: true
}
})
.state('login', {
url: '/login',
templateUrl: 'components/login/login.html',
controller: 'LoginController',
data: {
auth: false
}
})
})
And when user first time go to browser execute this code:
.run(['$rootScope','$http', '$state', 'UserService','userEmailGetService','isUserLoggedService',
function ( $rootScope,$http, $state, UserService,userEmailGetService,isUserLoggedService) {
UserService.CheckIfLogged()
.success(function(data) {
userEmailGetService.setUserEmail(data.email);
isUserLoggedService.setIsLogged(true);
stateCallback();
})
.error(function(data) {
//console.log(data);
isUserLoggedService.setIsLogged(false);
stateCallback();
$state.go("login");
});
this.stateCallback = function() {
$rootScope.$on("$stateChangeStart", function (event, toState, toParams, fromState) {
alert("Hello");
if (toState.data.auth == true && !isUserLoggedService.getIsLogged()) {
event.preventDefault();
$state.go("login");
}
if (toState.data.auth == false && isUserLoggedService.getIsLogged()) {
event.preventDefault();
$state.go("welcome");
}
});
};
}])
Routing in Firefox works ok but in Chrome when I logged and I go to login section, browser should ban this redirect and go to welcome section, when I run programmer console in chrome and I redirect to login panel when I logged evrythink works correct, very strange for me. When I added alert in stateChangeStart firefox show me this alert, chrome not, only when I open programmer panel.
You have a race condition as this is async:
UserService.CheckIfLogged()
.success(function(data) {
userEmailGetService.setUserEmail(data.email);
isUserLoggedService.setIsLogged(true);
stateCallback();
})
.error(function(data) {
//console.log(data);
isUserLoggedService.setIsLogged(false);
stateCallback();
$state.go("login");
});
This means that if the router is run before the validation code finishes, it will go to the login route instead of the welcome.
The solution is to use the resolve property of the routing, as it accepts promises, and will wait for the async response.
stateCallback is not defined because you assigned this function to this. Remove this from declaration this.stateCallback and then this code should work.
As title already suggests, I'm trying to disable some routes. I'm using angular seed project, that already has a nice structure.
I'm using JWT and I'm trying to set up a structure where if a certain route requires user to be logged in, and the user is not logged in, it redirects him to the some other page.
On my angular.module I've added the following code:
.run(['$rootScope', 'userService', '$location', function($rootScope, userService, $location) {
userService.init();
$rootScope.$on('$routeChangeStart', function(event, next, current) {
$rootScope.isPrivate = next['authenticate'];
if ($rootScope.isPrivate) {
if (!userService.get())
$location.path('/');
}
});
}]);
And this is a protected route:
angular.module('myApp.view2', ['ngRoute', 'ngCookies'])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/admin/vnos-stevilke', {
templateUrl: 'view2/view2.html',
controller: 'View2Ctrl',
authenticate: true
}).when('/admin/vnos-stevilke/:id', {
templateUrl: 'view2/view2.html',
controller: 'View2Ctrl',
authenticate: true
});
}])
.controller('View2Ctrl', ['$scope', 'webServices', '$location', '$routeParams', function($scope, webServices, $location, $routeParams) {
if ($routeParams.id)
webServices.getBranchById($routeParams.id, function(err, data) {
$scope.branch = data;
});
webServices.getCompanies(function(err, data) {
console.log(data);
console.log('no access!');
if (!err)
$scope.companies = data;
});
}]);
now at first it appears to be working OK: if I'm not logged in, the route is not displayed and I get redirected back to the root. But at a closer look I've noticed that console.log('no access!'); is still displayed in the console. So it appears that controller gets initialized.
It seems like the whole route is loaded and then gets redirected if user is not logged in. That is not the behaviour I'm looking for. I'm trying to HOLD the loading of the route until I'm sure the user is logged in.
Any ideas?
UPDATE:
I changed the code accordingly with the suggestion below, but it doesn't seem to work. Where have I gone wrong?
userService method that checks if user is logged in:
this.isLogged = function() {
var deferred = $q.defer();
if (current === null) return deferred.reject();
else return deferred.resolve(current);
};
Run method:
.run(['$rootScope', 'userService', '$location', function($rootScope, userService, $location) {
userService.init();
$rootScope.$on('$routeChangeError', function() {
$location.path('/');
});
}]);
Restricted page:
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/admin/vnos-stevilke', {
templateUrl: 'view2/view2.html',
controller: 'View2Ctrl',
resolve: function(userService) {
console.log('test');
return userService.isLogged();
}
});
}])
Here, the "test" never displays in console.
You need to just decide if you are letting user into restricted area with resolve route parameter.
If in one of resolve function resolves with a promise object that is rejected it stops entering requested route.
I would write something like:
$routeProvider.when('/restrictedURL',{
...some params,
resolve: function(userService){
return userService.get();
}
}
...and make userService.get return a Promise object that is resolved if session is active and rejected otherwise.
Now.. if promise is rejected a route won't be launched and $routeChangeError event is raised, so you need something like:
angular.module('yourapp').run(function($rootScope){
$rootScope.$on("$routeChangeError",function(){
$location.path('/');
});
});
read more about resolve parameter # https://docs.angularjs.org/api/ngRoute/provider/$routeProvider
I'm trying to implement basic authentication routing in AngularJS. I have a model that has a authorize method that returns a promise. I want the routing to wait until that authorize function has returned true or false to continue, once that has completed it should resume the path or redirect the user to the login page.
I think essentially i need to stop routing, call that method and then resume or redirect to login. Below is the code I have so far but i'm not sure how to accomplish the pause/resume. Any ideas?
return angular.module('d', ['ngCookies', 'ngRoute'])
.config(['$routeProvider', '$locationProvider', '$httpProvider',
function ($routeProvider, $locationProvider, $httpProvider) {
$routeProvider.when('/',
{
templateUrl: 'views/home.html',
controller: 'HomeCtrl'
});
$routeProvider.when('/login',
{
templateUrl: 'views/login.html',
controller: 'LoginCtrl'
});
$routeProvider.otherwise({ redirectTo: '/404' });
$locationProvider.html5Mode(true);
// handle unauthorized requests by redirecting to login page
$httpProvider.responseInterceptors.push(
['$location', '$q', function ($location, $q) {
function success(response) {
return response;
}
function error(response) {
if (response.status === 401) {
$location.path('/login');
return $q.reject(response);
}
else {
return $q.reject(response);
}
}
return function (promise) {
return promise.then(success, error);
}
}]);
}])
.run(['$rootScope', '$location', 'Auth', function ($rootScope, $location, Auth) {
$rootScope.$on("$routeChangeStart", function (event, next, current) {
$rootScope.error = null;
Auth.authorize().then(function(){
$location.path('/');
},function(){
$location.path('/login');
});
});
}]);
Your solution is very similar to a prototype I wrote a while back.
The idea is that whenever you "touch" the server and get an authentication error, a modal window pops-up asking for a login without changing the URL (you let it change to the new URL and stay there).
My implementation was also based on an interceptor checking for 401. It has a dependency on $rootScope and sets a property "needsLogin" to true. The page template has the login modal window visible when needsLogin === true and hides the ng-view (this is important since the new route has been loaded but it misses its data). Finally, upon successfull login, the login controller does the $route.reload() and then sets $rootScope.needsLogin = false.
Small snippets:
<div id="main" role="main" ng-show="needsLogin === false">
<div ng-view></div>
</div>
<div ng-show="needsLogin === true" ng-include="'views/login.html'" class="overlay"></div>
The login controller can be something like:
function LoginCtrl($scope, $rootScope, $route, $http) {
$scope.login = function () {
$http.post( /* Somehow do your login */ )
.success(function () {
var deregister = $rootScope.$on('$routeChangeSuccess', function () {
// hide login / show ng-view after route has been reloaded
$rootScope.needsLogin = false;
deregister();
});
$route.reload();
})
.error(function () {
/* handle errors */
});
};
}
$route.reload() is not a full page refresh, it merely re-initializes the route (controller/view etc). Hopefully, the call that was rejected before will run again and the page will be fine.