I have angular ui-router and I with to restrict access to a view unless a cookie is active.
I set my cookie in a post where i set cookie to users email address.
I know wish to only allow user to a view if they have a cookie username.
How do I do this ?
Setting cookie
FirstModule.controller('LoginController', function ($scope, $http, $cookies, $location) {
$scope.sometext = {};
$scope.LoginForm = function () {
var data = {
LoginEmail: $scope.sometext.LoginEmail
};
$http({
url: 'http://localhost:8080/back-end/controller',
method: "POST",
data: data,
headers: {'Content-Type': 'application/json'}
}).then(function (response) {
if (response.status === 200)
// $scope.sometext = "You are a valid member, please proceed to Mock Up Maker";
$cookies.put('UserName', $scope.sometext.LoginEmail);
$location.path('/download');
// console.log($cookies);
}).catch(function (response) {
if (response.status === 400)
$scope.sometext = "Hmm, it seems you are not registered, try again or register";
else if (response.status === 404)
$scope.sometext = "this is non a valid email address, please check email";
else if (response.status === 500)
$scope.sometext = "No API connection. Server side fail ";
else $scope.sometext = "Server connection error, give it a second to establish connection then try again";
});
}
});
router
FirstModule.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
// route to show our basic form (/form)
.state('form', {
url: '/form',
templateUrl: 'views/form.html',
controller: 'formController'
})
// nested states
// each of these sections will have their own view
// url will be nested (/form/signup)
.state('form.signup', {
url: '/signup',
templateUrl: 'views/form-signup.html'
})
// url will be /form/select
.state('form.select', {
url: '/select',
templateUrl: 'views/form-select.html'
})
// url will be /form/type
.state('form.type', {
url: '/type',
templateUrl: 'views/form-type.html'
})
// catch all route
// send users to the form page
$urlRouterProvider.otherwise('/form/signup');
});
You can use the resolve property from ui-router, just add the field resolve in your state definition and if the condition is not met redirect the user to a different state (eg. the login state)
state('user', {
url: '/',
templateUrl: 'UserView.html',
controller: 'UserViewController',
resolve: {
check: function($q, $cookies) {
if ($cookies.get('UserName')){ //cookie to check
return $q.resolve({});
} else{
return $q.reject({redirectState: 'loginState'});
}
}
}
});
then add an error handler to detect any error
$rootScope.$on('$stateChangeError', function(evt, to, toParams, from, fromParams, error) {
if (error.redirectState) {
$state.go(error.redirectState);
}
})
The answer given by Karim is good and should work well. However, this is like taking action at second step in flow.
The best way to handle this is to provide user a way to navigate to this URL only if he has that cookie set. You can either disable the link or hide it completely. IMO, that is a better approach.
I's rather implement it both ways to make my application robust. That implies, disabling/removing link/button will prevent users from accessing the link at all. And the Router-Resolve will safeguard from smart users who can copy paste the link or hit enter/F5 on browser. :)
Related
.state('newProduct', {
url: '/products/new',
templateUrl: 'app/products/templates/product-new.html',
controller: 'ProductNewCtrl',
authenticate: 'cook,admin'
})
I'm trying to add different client routes based on role authentifications but if I try to add another role such as cook for example it won't trigger the page defined by the url for both of them. It will work separately tho if that makes more sense
authenticate: 'cook',
authenticate: 'admin'
is this a syntax error?
In your .state block, 'authenticate' is simply a data holder
You will need to do a manual check on the value of authenticate to handle permissions.
For example:
myApp.config(function($stateProvider, $urlRouterProvider){
$stateProvider
.state("newProduct", {
url: '/products/new',
templateUrl: 'app/products/templates/product-new.html',
controller: 'ProductNewCtrl',
authenticate: true
})
});
Where you require to check permissions, you you will need access the $state u want using the $state object.
for example in your controller inject the $state object and use:
if($state.get('newProduct').authenticate){ //if(true) in this case)
//do what u want
}
If you want to check permissions everytime u change state/screen heres an example for that too:
angular.module("myApp").run(function ($rootScope, $state, AuthService) {
$rootScope.$on("$stateChangeStart", function(event, toState,
toParams,fromState, fromParams){
if (toState.authenticate){
// User is authenticated
// do what u want
}
});
});
found the fix thanks to Alon indexOf it got me thinking :)
.run(function($rootScope, $state, Auth) {
// Redirect to login if route requires auth and the user is not logged in
// also if the user role doesn't match with the one in `next.authenticate`
$rootScope.$on('$stateChangeStart', function(event, next) {
if (next.authenticate) {
var loggedIn = Auth.isLoggedIn(function(role) {
if (role && next.authenticate.indexOf(role[0]) !== -1) {
console.log('works')
return; // logged in and roles matches
}
event.preventDefault();
if(role) {
// logged in but not have the privileges (roles mismatch)
console.log(next.authenticate.indexOf(role[0]));
$state.go('onlyAdmin');
} else {
// not logged in
$state.go('login');
}
});
}
});
});
I'm not sure if this is a duplicate or not, but I didn't manage to find anything that worked for me, so I'm posting this question.
I have a situation where I need to get values from database before directing user to certain routes, so I could decide what content to show.
If I move e.preventDefault() right before $state.go(..) then it works, but not properly. Problem is that it starts to load default state and when it gets a response from http, only then it redirects to main.home. So let's say, if the db request takes like 2 seconds, then it takes 2 seconds before it redirects to main.home, which means that user sees the content it is not supposed to for approximately 2 seconds.
Is there a way to prevent default at the beginning of state change and redirect user at the end of state change?
Also, if we could prevent default at the beginning of state change, then how could we continue to default state?
(function(){
"use strict";
angular.module('app.routes').run(['$rootScope', '$state', '$http', function($rootScope, $state, $http){
/* State change start */
$rootScope.$on('$stateChangeStart', function(e, to, toParams, from, fromParams){
e.preventDefault();
$http
.get('/url')
.error(function(err){
console.log(err);
})
.then(function(response){
if( response.data === 2 ){
// e.preventDefault()
$state.go('main.home');
}
// direct to default state
})
}
}]);
});
You could add a resolve section to your $stateProviderConfig.
Inside the resolve you can make a request to the databse and check required conditions. If case you don't want user to acces this page you can use $state.go() to redirect him elsewhere.
Sample config:
.state({
name: 'main.home',
template: 'index.html',
resolve: {
accessGranted: ['$http', '$state', '$q',
function($http, $state, $q) {
let deffered = $q.defer();
$http({
method: 'GET',
url: '/url'
}).then(function(data) {
if (data === 2) {
// ok to pass the user
deffered.resolve(true);
} else {
//no access, redirect
$state.go('main.unauthorized');
}
}, function(data) {
console.log(data);
//connection error, redirect
$state.go('main.unauthorized');
});
return deffered.promise;
}
]
}
});
Documentation of the resolve is available here
Note that you could use Promise object instead of $q service in case you don't need to support IE
One way to handle this situation is adding an interceptor as follows.
.config(function ($httpProvider) {
$httpProvider.interceptors.push('stateChangeInterceptor');
}).factory('stateChangeInterceptor', function ($q, $window,$rootScope) {
return {
'response': function(response) {
var isValid = true;//Write your logic here to validate the user/action.
/*
* Here you need to allow all the template urls and ajax urls which doesn't
*/
if(isValid){
return response;
}
else{
$rootScope.$broadcast("notValid",{statusCode : 'INVALID'});
}
},
'responseError': function(rejection) {
return $q.reject(rejection);
}
}
})
Then handle the message 'notValid' as follows
.run(function($state,$rootScope){
$rootScope.$on("notValid",function(event,message){
$state.transitionTo('whereever');
});
})
I have the following question... or situation. I have states defined in my AngularJS app, like so...
$stateProvider
.state('myApp', {
abstract: true,
template: '<ui-view/>'
})
.state('myApp.stateOne', {
url: 'state1',
templateUrl: '/an/views/state-1.html',
controller: 'StateOneCtrl'
})
.state('myApp.stateTwo', {
url: 'state2',
templateUrl: '/an/views/state-2.html'
controller: 'StateTwoCtrl'
})
.state('myApp.stateThree', {
url: 'state3',
templateUrl: '/an/views/state-3.html'
controller: 'StateThreeCtrl'
})
There are more states and I have changed the naming for this example, but suppose I need to check if the user is allowed to see / load 'mayApp.stateThree'. I can determine this by asking the backend. I have a service (in this example called IsAllowedService) to deal with this requests / provide the access and normally I would write the logic to do the check in the .run() block in my app.js file for example:
.run(['IsAllowedService', '$state', function (IsAllowedService, $state) {
$rootScope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState) {
// check if we are going to sfm.addContacts and if we are allowed to...
if (toState.name === 'myApp.stateThree') {
IsAllowedService.checkIfIsAllowed().then(function (resp) {
if(resp.allowed === false) {
$state.go('myApp.stateOne');
}
});
}
});
}]);
This works well but doesn't wait until we get the result from the service so 'mayApp.stateThree' is loaded then we a redirected if necessary. So we get a quick flash of the page before we are redirected. I could put the same code into the 'StateThreeCtrl' but I still get the flash / FOUC. Would it be possible to resolve this when defining the states, I know this won't work but something like this...
.state('myApp.stateThree', {
url: '/an/state3',
templateUrl: '/an/views/state-3.html'
controller: 'StateThreeCtrl',
resolve: {
isAllowed : function () {
IsAllowedService.checkIfIsAllowed().then(function (resp) {
return resp;
})
}
}
I realise that I wouldn't be able to inject the service (or even the $http service) but is it possible for me to somehow pause the loading of the view / controller of 'mayApp.stateThree' until I get the result from IsAllowedService.checkIfIsAllowed(). Any advice on how to structure my app / code would be appreciated. I have used ng-cloak in my HTML view but this did nothing!
Actually you're doing it almost right in the application's run block. Except you are not preventing anything. You can achieve that by adding:
event.preventDefault(); //Prevent from going to the page
Furthermore, you can add custom data to your $states , which will allow you to verify those conditions with your criteria. e.g.:
$stateProvider.state('home', {
controller: 'HomeController as home',
url: '/home',
templateUrl: 'home.html',
data: { roles: [ROLES.ANONYMOUS] }}); //This can be any condition
$stateProvider.state('user', {
controller: 'UserController as user',
url: '/user',
templateUrl: 'user.html',
data: { roles: [ROLES.ADMIN, ROLES.USER] }});
You can retrieve this custom data in the $stateChangeStart event:
$rootScope.$on('$stateChangeStart', function (event, next) {
if (!yourService.isAuthorized(next.data.roles)) {
event.preventDefault(); //Prevent from going to the page -> no flickering
$state.go('403'); //Or whatever is desired.
}
});
You see the flickering because you're using a Promise and the first page only gets redirected when the promise is furfilled. You can stop the flickering by preventing the default action, authorize and continue your flow as you desire when the promise resolves.
if (toState.name === 'myApp.stateThree') {
event.preventDefault(); //preventing the request.
IsAllowedService.checkIfIsAllowed().then(function (resp) {
if(resp.allowed === false) {
$state.go('myApp.stateOne');
} else { //he actually is allowed to go to state three.
$state.go('myApp.stateThree');
}
}, function() { //in case the server has no answer
$state.go('myApp.stateOne'); //you probably want to prevent it too
} );
In my opinion, if these conditions do not change during runtime, i.e. user role based, you can retrieve them upon user verification so you don't need a promise to begin with. Hope this helps.
I made a similar post before and added a working plunker.
Here's my case scenario:
User is not logged in, and they try to access a /settings page.
My Settings controller recognizes based on $auth.isAuthenticated() != true that they aren't logged in, and redirects them to /login
User fills out their email and password and hits submit.
What I would like to do on this third step is then redirect them to the /settings page, not the home page.
I'm thinking I would be changing this variable:
$authProvider.loginRedirect = '/';
The problem is that I cannot include $authProvider in my loginCtrl.js file without getting an "unknown provider" error in my console: https://docs.angularjs.org/error/$injector/unpr?p0= In other words, Angular does not recognize $authProvider when I try to include it. Here's what my loginCtrl.js file looks like:
/* Everything is blowing up because I'm trying to include $authProvider */
angular.module("PrayerChain")
.controller("loginCtrl", ["$rootScope", "$scope", "$state", "$http", "$auth", "$authProvider", loginCtrl]);
function loginCtrl($rootScope, $scope, $state, $http, $auth, $authProvider) {
$authProvider.loginRedirect = '/settings';
$scope.login = function () {
$auth.login({ Email: $scope.email, Password: $scope.password })
.then(function() {
})
.catch(function (response, status, headers) {
console.log(response);
$scope.error = JSON.parse(response.data);
});
};
}
Is including $authProvider in a controller even possible? If not, what's an alternative solution to changing where people are redirected upon logging in using Satellizer?
Thanks.
Usually provider objects can only be accessed at config time, whereas controllers are created in runtime. If you need to setup the authProvider, try doing:
angular.module('PrayerChain').config(
[ "$authProvider",
function($authProvider) {
$authProvider.loginRedirect = '/settings';
}
]).controller("loginCtrl",
// ...
The new version (0.12.5) are not using this settings anymore. You need to set the url inside your controller
$auth.login({ Email: $scope.email, Password: $scope.password })
.then(function() {
$location.path('your-new-route');
})
.catch(function (response, status, headers) {
console.log(response);
$scope.error = JSON.parse(response.data);
});
I was looking to do this and found that in version 0.13.0 (maybe earlier too?) you can pass an options parameter to login function like this:
$auth
.login(user, {
url: config.api + '/authenticate/customer'
})
I'm using ui-router in my angular application. Currently I've two routes /signin & /user.
Initially it shows /signin when the user clicks on the login button, I'm sending a ajax request and getting the user id. I'm storing the user id in localstorage and changing the state to /user.
Now, what I want, if a user is not loggedin, and user changes the addressbar to /user, it'll not change the view, instead it'll change the addressbar url to /signin again.
I'm try to use resolve, but it's not working. My code is:-
module.exports = function($stateProvider, $injector) {
$stateProvider
.state('signin', {
url: '/signin',
template: require('../templates/signin.html'),
controller: 'LoginController'
})
.state('user', {
url: '/user/:id',
template: require('../templates/user.html'),
resolve:{
checkLogin: function(){
var $state = $injector.get('$state');
console.log("in resolve");
if (! window.localStorage.getItem('user-id')) {
console.log("in if")
$state.go('signin');
}
}
},
controller: 'UserController'
})
}
Please help me to solve this problem.
I don't think it's allowed to change states in the middle of a state transition.
So, the way to address it is to have the checkLogin resolve parameter (I changed it below to userId) to be a function that either returns a value or a promise (in this case, a rejected promise, if you can't get the user-id).
You'd then need to handle this in $rootScope.$on('$stateChangeError') and check the error code.
resolve: {
userId: function ($q, $window) {
var userId = $window.localStorage.getItem('user-id');
if (!userId) {
return $q.reject("signin")
}
return userId;
}
}
And redirect in the $stateChangeError handler:
$rootScope.$on('$stateChangeError', function (event, toState, toParams, fromState, fromParams, error) {
if (error === "signin") {
$state.go("signin");
}
});
If someone has this problem, you can solve it, using timeout service. It will put state switching call at the end of queue.
Also, you should use promises. Rejecting it will prevent initialization of that state:
resolve:{
checkLogin: function(){
var deferred = $q.defer();
var $state = $injector.get('$state');
if (!window.localStorage.getItem('user-id')) {
$timeout(function(){$state.go('signin');});
deferred.reject();
} else {
deferred.resolve();
}
return deferred.promise;
}
},