Angular route async authorization - angularjs

Angular doesn't provide any authorization/access permission on routing (I'm talking default Angular route 1.x and not beta 2.0 or UI route). But I do have to implement it.
The problem I'm having is that I have a service that calls server to provide this info and returns a promise. This data however is only obtained once and then cached on the client, but it still needs to be obtained once.
I would now like to handle $routeChangeStart event that checks whether next route defines a particular property authorize: someRole. This handler should then get that data using my previously mentioned service and act accordingly to returned data.
Any ideas beside adding resolves to all my routes? Can I do this centrally somehow? Once for all routes that apply?
Final solution
With the help of accepted answer I was able to implement a rather simple and centralized solution that does async authorization. Click here to see it in action or check its inner working code.

The most simple way is to deal with current route's resolve dependencies, and $routeChangeStart is a good place to manage this. Here's an example.
app.run(function ($rootScope, $location) {
var unrestricted = ['', '/login'];
$rootScope.$on('$routeChangeStart', function (e, to) {
if (unrestricted.indexOf(to.originalPath) >= 0)
return;
to.resolve = to.resolve || {};
// can be overridden by route definition
to.resolve.auth = to.resolve.auth || 'authService';
});
$rootScope.$on('$routeChangeError', function (e, to, from, reason) {
if (reason.noAuth) {
// 'to' path and params should be passed to login as well
$location.path('/login');
}
});
});
Another option would be adding default method to $routeProvider and patching $routeProvider.when to extend route definition from default object.

ui-router have a lot of events that you can easy manipulate. I always use it.
State Change Events have everything you need. Something like this will be implement in the AngularJS 2.x.
But if you are looking the solution for native Angular 1.x.y router this solution will not help you. Sorry

If you can use ui-router, you could do this:
.state('root', {
url: '',
abstract: true,
templateUrl: 'some-template.html',
resolve: {
user: ['Auth', function (Auth) {
return Auth.resolveUser();
}]
}
})
Auth.resolveUser() is just a backend call to load the current user. It returns a promise so the route will wait for that to load before changing.
The route is abstract so other controllers must inherit from it in order to work and you have the added benefit that all child controllers have access to the current user via the resolve.
Now you catch the $stateChangeStart event in app.run():
$rootScope.$on('$stateChangeStart', function (event, next) {
if (!Auth.signedIn()) { //this operates on the already loaded user
//user not authenticated
// all controllers need authentication unless otherwise specified
if (!next.data || !next.data.anonymous) {
event.preventDefault();
$state.go('account.login');
}
}else{
//authenticated
// next.data.roles - this is a custom property on a route.
if(!Auth.hasRequiredRole(next.data.roles)){
event.preventDefault();
$state.go('account.my'); //or whatever
}
}
});
Then a route that requires a role can look like this :
.state('root.dashboard', {
//it's a child of the *root* route
url: '/dashboard',
data: {
roles: ['manager', 'admin']
}
...
});
Hope it makes sense.

I've approached this issue many times, I've also developed a module (github).
My module (built on top of ui.router) is based on $stateChangeStart (ui.router event) but the concept is the same with the default ng.route module, it's just a different implementation way.
In conclusion I think that handling routing changing events is not the good way to perform an authentication checking:
For example, when we need to obtain the acl via ajax the events can't help us.
A good way, I think, could be to automatically append a resolve to each "protected" state...
Unfortunately ui.Router doesn't provides an API to intercept the state creation so I started my module rework with a little workaround on top of $stateProvider.state method.
Definitively, I'm looking for different opinions in order to find the correct way to implement a Authentication Service in AngularJS.
if are there anyone that is interested in this research... please, open an issue on my github and the discuss

Related

How to pass data in ui-router through external redirects?

How can I pass data to my angular project using external redirects?
I need to pass mdToast messages to my project from an external source. I need to intercept these messages at all the controllers. Currently, I am using query strings to achieve this right now. How can I intercept this query string in the runBlock? I want to somehow intercept it at all routes.
Here's how I am sending the query string:
$stateProvider
.state('toastIntercept', {
url: '/toastIntercept?message',
templateUrl: 'views/redirect-by-intercept.html',
controller: 'redirectController'
});
On further note, here's what I am actually trying to do:
I understand discussions are discouraged here but I'd really appreciate some inputs or some resources or articles on this. I haven't been able to find any credible ones.
I generate emails to my users using node. Users can take some actions through that email but ultimately land on my static angular project. What would be the right way to achieve this?
I can think of two possible ways:
Using a Proxy that handles the email redirection, performs the actions and redirects to the angular project using a toast message as a query string to notify the user about his action.
Instead of the Proxy, handle the email redirects on my static angular project itself using one of the routes. This however feels like an anti-pattern to me since a static project shouldn't be handling API level tasks.
If I understand you correctly, I think you could solve this problem by adding an interceptor.
.config(function($httpProvider) {
//Register the interceptor via an anonymous factory
$httpProvider.interceptors.push(function($q, dependency1, dependency2) {
return {
'request': function(config) {
// same as above
},
'response': function(response) {
// same as above
}
};
});
});
Yo could add interceptos for both requests and reponses.
Other possible values for the interceptos are: 'requestError' and 'responseError'
You could Also define a factory and then do the following
$httpProvider.interceptors.push('connectionFactoryInterceptor');

Multiple states and urls for the same template

I'm building a web application and I have a screen that consists in five sections, each section represents a level, the areas are the higher level of my tree, when I click in any card of the area, the system should return the skills of that area and so on.
I need to change the url and state according what the user is accessing, for example, if the user access some skill, the url must be
example.com/#/curriculum/skill/<skillId>
and if I access this link it should automatically load the capabilities from this skill and his parent which is area in this case.
I have one controller for area, skill, capability, knowledge and criteria, in each controller I have a action to load the next level of the tree, which looks like that
$scope.loadSkills = function (id) {
Area.loadSkills(...)
$state.go('curriculo.skill', {id: this.current.id}, {nofity: false, reload: false});
}
And these are my states
$stateProvider
.state('curriculum', {
url: '/curriculum',
templateUrl: '/templates/curriculo.html',
})
.state('curriculum.are', {
url: '/area/:id',
template: '',
})
.state('curriculum.skill', {
url: '/skill/:id',
template: '',
})
.state('curriculum.capability', {
url: '/capability/:id',
})
.state('curriculum.knowledge', {
url: '/knowledge/:id',
})
.state('curriculum.criteria', {
url: '/criteria/:id',
});
I'm new in Angular and I not sure about what to do, should I created multiple nested views in this case, and if so, how do I load stuff that I need according the url?
I would suggest to use the capability of multiple named views offered by the ui-router. You can read more about it here. Basically the documentation says the following:
You can name your views so that you can have more than one ui-view per
template.
If you check the example in the documentation, you'll notive that there are similarities between your scenario and the example, because you want to dynamically populate a different views (here named views).
Example
I tried to recreate your scenario in this JSFiddle.
First I created an abstract state which provides the different views like areas, skills etc. This is the template for the abstract state:
<div class="curriculum" ui-view="areas"></div>
<div class="curriculum" ui-view="skills"></div>
Next I created a nested state curriculo.main, which declares the different views (areas, skills etc.) you need. Each view has its own template and controller. Notice that the nested state has a resolve which initially loads the areas from a service called curriculo. If you use resolves remember that the resolve keyword MUST be relative to the state not the views (when using multiple views).
Basically the service is responsible for the business logic, means getting the areas, skills etc. In the JSFiddle I have hard-coded the HTTP results. Replace that with HTTP calls and make use of promises. Since each named view has its own controller we need a mechanism to notify about changes, for example to notify the SkillsController that skills have been loaded. Thus, I created a simple event system (subcribe-notify):
.factory('notifier', function($rootScope) {
return {
subscribe: function(scope, callback, eventName) {
var handler = $rootScope.$on(eventName, callback);
scope.$on('$destroy', handler);
},
notify: function(eventName, data) {
$rootScope.$emit(eventName, data);
}
};
});
The SkillsController can then subscribe to a specific event like so:
notifier.subscribe($scope, function(event, data) {
$scope.skills = data;
}, 'onSkillsLoaded');
The curriculo service calls (at the end of the getSkills()) notifyand provides an event. In this case the same event as you subscribed to in the SkillsController.
notifier.notify('onSkillsLoaded', result);
All in all, that's the magic behind my little example. It's worth mentioning that you need to apply best practices to the code, since this is just to recreate your scenario. For best practices I suggest the Angular Style Guide by John Papa.
Update 1
I updated my example to show you deep linking. I simulate the deep link via
$state.go('.', {area: 2, skill: 5});
This way I can activate a certain state. Now each view has its activate function. Inside this function I do all the work that is neseccary for the initialization, e.g. selecting an area if the query param is set. As you know, you can access the params with the $state service. I had to use a $timeout to delay the init of the areas controller because the subscribe wasn't made yet. Please try to find a better solution to this problem. Maybe you can use promises or register each controller in a service which resolves if all controller have been initialized.
If anything has been selected I also use the go with an additional option to set the notify to false.
$state.go('.', {area: area.id, skill: skillId ? skillId : undefined}, {notify: false});
If notify is set to false it will prevent the controllers from being reinitialized. Thus you can only update the URL and no state change will happen.

Init application waiting for REST call data result

Having created some basic web applications with Angular, still learning and discovering ...
I need to create a Angular application which depends on various parameters used in the application. For example various label string we will retrieve through REST... These labels are used on every screen of the application, including the first screen that is shown on start-up...
I'm looking for a 'proper' / 'good' / 'angular' way to init the application waiting for the result of a sort of 'init-application' rest-call - due to the fact that the result of this rest call contains data the application will need from start on, I have found an example approach below ...
https://blog.mariusschulz.com/2014/10/22/asynchronously-bootstrapping-angularjs-applications-with-server-side-data
Are there any more ideas / thoughts / useful links ...
Thanks!
I would suggest you to explore the 'resolve' property of ngRoute(if you are using ngRoute) or UI-router state:-
While using $routeProvider:-
$routeProvider
.when("/news", {
templateUrl: "newsView.html",
controller: "newsController",
resolve: {
message: function(messageService){
return messageService.getMessage();
}
}
})
Or, if you are using UI Router :-
$stateProvider.state('root',{
abstract:true, // this is abstract statte, controller is in child states.
templateUrl:'/assets/root-module/partial/main/main.html',
resolve : {
securityContext : function($http){
return $http.get("/security/context");
},
token : function(securityService,securityContext){
securityService.setToken(securityContext.data.token);
console.debug(securityContext.data.token);
return securityContext.data.token;
}
}
});
In either of the approaches, just make sure that you DO NOT provide 'ng-controller' in your HTML templates. Rather provide the controller while defining your route|state as the above example suggests.
The properties being "resolved" - if they are promises, the controller (hence the view) won't be initialized until the promise is resolved.
I highly recommend you to explore UI Router state hierarchy approach that allows you to share 'resolved; proerties to the child states.

Multi-steps register form in Angularjs

I'm building an app in which I've got a registration flow divided in 3 parts, Personal Info, Profile picture, and so on, each one in its invidual view.
So, which way do you think is the best way to persist data between views so in the final step I can show the whole data to the user and ask for confirmation.
Local Storage? $cacheFactory? I've really have no idea how to do it, any advice will be usefull!
Thanks in advance
cookie or local storage is available,but I think Angular way is recommended.
$rootScope: you can storage user data in $rootScope simply use
$rootScope.userData = {
'userName' : 'first',
'userId' : 'second'
};
to storage and read data in new view, remember to inject $rootScope into your controller
$broadcast: angular provide event $broadcast and $emit, so you can use $on to watch event and get your user data.and this is a demo http://jsfiddle.net/whitewolf/5JBA7/15/
route resolve? : if you send user data in every step to database,
you can use resolve in route config to load user data by $http.get().
add a service
app.factory("messageService", function($q){
return {
getMessage: function(){
return $q.when("Hello World!");
}};
});
in route config
$routeProvider
.when("/news", {
templateUrl: "newsView.html",
controller: "newsController",
resolve: {
message: function(messageService){
return messageService.getMessage();
}}
})
in your controller
app.controller("newsController", function (message) {
$scope.message = message;
});
You are looking for an angular wizard
if you are using ui-router, you can easily do this with nested states
have a look
http://scotch.io/tutorials/javascript/angularjs-multi-step-form-using-ui-router
one form ( main state )
separate templates (nested states)
Note: specify controller for main state only and not for nested states
It will depend on the requirement.
If there is a requirement that the user can complete two steps and
come back later to complete the third step, then better go with local
persistence.
But if it's really not the case, you need not use any persistence
mechanism. Only thing is you need to properly map the models. And on the final submit, you can persist the complete input data.

Access scope or service in ui router onEnter to check access (auth)

Ok, so I have a state for the url "/securepage" which I need to check whenever a user tries to access it. So I read there is an onEnter function I can use. But I cannot seem to get hold on the scope nor a service from there. What am I doing wrong?
.state('securepage', {
url: "/securepage",
template: securepage.html,
onEnter: function(){
// if (!$scope.main.isAuthenticated) $state.go("/login");
// if (!myLoginService.currentUser()) $state.go("/login");
Current options as I see it are to use resolve and/or check the authentication in the controller. But wouldnt an auth check be better placed in onEnter?
I ran into a similar problem today. Spent a whole day and finally came up with a workable solution other than the ones already suggested here.
My main goal is to find easy and effective way to selectively secure certain specific webpages. The security check needs to be performed before the HTML or any of the relevant controllers are loaded or called. If the check fails, the page may be forwarded to elsewhere without any side effects from the other controllers.
I tried the other suggested approaches. Each one has their own set of problems:
Using OnEnter:
There is no way to block ui-router from continuing with the state transition while making an async call to do the security check.
Using $rootScope.$on('$stateChangeStart'):
The management of the states that rquire security check will become separated from the $stateProvider.state() definitions. Ideally, I would rather see everything about the definition of a state defined all in one place. Though this is not a showstopper, it is not ideal.
A much bigger problem is that the $stateChangeStart event is not being called for the initial loading of a page. This one is a showstopper.
My solution is to use a resolve function to define a promise that will cause the view controllers to wait for the deferred to complete before they are called. This work perfectly for blocking the controller from starting in an asynchronous fashion.
Here's a rough outline of the code I used:
.config(['$stateProvider', function ($stateProvider) {
// Handler for Restricting Access to a page using the state.resolve call
var accessRestrictionHandler = function($q, $rootScope, $state) {
var deferred = $q.defer();
// make sure user is logged in
asyncCheckForLogin(function(status) {
if (status != "Logged In") {
// You may save target page URL in cookie for use after login successful later
// To get the relative target URL, it is equal to ("#" + this.url).
// The "this" here is the current scope for the parent state structure of the resolve call.
$state.go("loginPage");
}
else // if logged in, continue to load the controllers. Controllers should not start till resolve() is called.
deferred.resolve();
}.bind(this));
return deferred.promise;
};
$stateProvider
.state('userProfile', {
url: '/userProfile',
views: {
'main': {
templateUrl: 'userProfile.html',
controller: 'userProfileCtrl'
}
},
// SIMPLY add the line below to all states that you want to secure
resolve: { loginRequired : accessRestrictionHandler }
})
.state(.... some other state)
.state(.... some other state);
}]);
I hope this will help some of you out there.
Another approach would be to have a service/controller listen to the "$stateChangeStart" event. There, you could check if the called state needs authentication and reroute the request. Here's a snippet:
$rootScope.$on('$stateChangeStart', function (event, nextState, currentState) {
if (!isAuthenticated(nextState)) {
console.debug('Could not change route! Not authenticated!');
$rootScope.$broadcast('$stateChangeError');
event.preventDefault();
$state.go('login');
}
});
isAuthenticated could hold the call to your services, check nextState.data for authentication-related properties, etc.
Check out this issue and this example from their github page. It should give you some clues.
A late answer but well I'd rather write it anyway. I prefer not touching the $rootScope wherever possible. Here's the code that I'm currently working on which sums up another solution for your question:
state('dash', {
url:'/',
views:{
"topNav":{
templateUrl:"user/dash/partials/top-nav.html",
controller:'logoutCtrl',
},
"sideNav":{
templateUrl:"user/dash/partials/side-nav.html"
},
"body":{
templateUrl:"user/dash/partials/body.html",
controller:'testCtrl'
}
},
onEnter: function(Auth, $state){
if(!AuthSvc.isAuthenticated){
$state.go('login');
}
}
})
I'm using JWTs to store the tokens on the localstorage by using ngStorage which gives a $localStorage service which I inject in the Auth factory I injected in onEnter
probably you mean
.state('securepage', {
url: "/securepage",
template: securepage.html,
//this is how you actually inject things to onEnter property
onEnter: ['$state','myLoginService',function($state,myLoginService){
if (!$scope.main.isAuthenticated) $state.go("/login");
if (!myLoginService.currentUser()) $state.go("/login");`}]
....
I am using angularjs 1.3 with ui.router 0.2.10
I'm sure a lot has changed since this question was asked but as I had to figure it out myself and my search led me here...
As far as checking authentication you could do it like this
.state('securepage', {
url: "/securepage",
template: securepage.html,
onEnter: function($scope,$state,myLoginService){ //THIS IS THE CHANGE
if (!$scope.main.isAuthenticated) $state.go("/login");
if (!myLoginService.currentUser()) $state.go("/login");
You can put pretty much provider/service/factory in the onEnter function to gain access to it, and this does work from inside the .config of the app.
Having said that, they (ui-router makers) suggests to use custom rule function handle it inside .$on('$stateChangeStart', function(e, to) https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-create-rules-to-prevent-access-to-a-state
I don't know why they do this instead of onEnter, maybe someone could expand on that.

Resources