Currently I'm implementing ng-token-auth into my Angular application, while this works great, I'm having some trouble with restricting access to certain pages.
In some of my routes I have a couple of extra parameters:
data: {
title: 'Dashboard',
restricted: true, // Only allow logged in users
role: 2 // Only allow a specific role
}
I'm doing this checking login in $stateChangeStart, so before I switch routes, I can check if the user is allowed to that route.
I followed the ng-token-auth suggestions about using a parent route with a resolve to check if a user is logged in or not:
resolve: {
auth: function($auth) {
console.log('validate user');
return $auth.validateUser();
}
}
Now the problem comes when I first load up the application, obviously the $stateChangeStart event is fired before the $auth.validateUser() has been resolved, because of that the login inside the $stateChangeStart fails and the user is redirected to the login page.
What would be the better way of implementing this "permissions logic", I don't want to do it per route, as that would add in a lot of extra work and code.
Doing it in the $stateChangeStart also doesn't seem to be the best options as that doesn't work on first load.
I would treat Authentication and Authorization as two different things.
ng-token-auth helps you with Authentication. It even helps you with selecting which routes must be available for authenticated users Refer to example-using-angular-ui-router
role: 2 // Only allow a specific role
seems more of authorization and permissions. For that you may want to take a different approach. One such approach. We took a similar approach - we also made sure some of the authorization was fetched upfront.
Related
I´m currently developing an application based on user authentication where each user can register a student-campus as a teacher and currently, I'm on a feature where I have two routes:
Route 1: It has a Datagrid where I'm listing all of the student campuses that I've already created and each row has an edit button that navigates to "Route 2" and the purpose of that is to edit the already created student campus.
Route 2: It has a form with all the necessary fields to create a student-campus.
As you can see I need to pass the student-campus ID to fetch data in the ngOnInit to fill the fields and be able to edit the above-mentioned, so I have several options in consideration:
Option 1: Pass ID in the URL.
this.router.navigate(['planteles/registrar', idPlantel]);
https://myapplication/planteles/registrar/1
Option 2: Pass ID in the URL with queryParams.
this.router.navigate(['planteles/registrar'], { queryParams: { ID: idPlantel } });
https://myapplication/planteles/registrar?ID=1
Option 3: Pass ID in the state object of navigation extras.
this.router.navigate(['planteles/registrar'], { state: { id: idPlantel } });
Option 4: Shared service and BehaviorSubject to subscribe to data.
I owe you the code
I'm able to use any of these but I have a problem with each one of them.
I can't use Option 1 and Option 2 because the ID cannot be changed by the teacher because that gives him the possibility to fetch the student-campus data of another teacher and edit it, so it isn't safe.
The problem with option 3 and option 4 is when I refresh the page the state is lost.
Currently, I have a workaround with option 3 which is to redirect the user to the previous page if the state is undefined but I don't like that solution. I'd like to persist data if the user reloads the page without using LocalStorage.
Thanks in advance, all help or contribution is well appreciated.
Option 1 is the correct option here (and the way you will find most sites in the real world are implemented... including this one we're on now). The problem is your approach to web security, and what you need to fix is your backend. You're approaching web security as though front end security is real, it's not. Web security exists on your backend. Users should not be able to fetch or view or manipulate data that does not belong to them, and this must be enforced by your backend.
A high level example of how this might work: some secure authentication token should be granted when the user logs in, then this authentication token should be attached to each request. The API then uses this token to check which user is making the request and ensures they have the proper permissions. If they do not (as in the case of the user editing their URL param to some ID they do not have permissions for) or if there is no token, the API should return a 401 or 403 response and the front end should handle it appropriately (ie sending them back to list, or showing an error page, whatever you decide)... how to issue this token, make it secure, and make use of it is an entirely separate topic beyond the scope of this answer.
In any of the options, I could open my dev tools, and view any API requests being made, and change the ID's and use that to view or manipulate other people's data without any effort at all. So options 3 / 4 are barely more "safe" than 1 or 2. As none of these are safe without properly implemented backend security.
Front end "security" exists only as user experience. Me and you are both using the same URL to view this page, but we see different options and buttons, like you can edit or delete your post and accept answers, while I can't, but I can edit or delete my answer etc. This isn't for true security purposes, SO's servers enforce who can and can't take what actions. It's just showing me and you the UI that reflects our different permissions, ie, its all just UX.
There's another way too, which is defined in Angular docs itself.
NavigationExtras
Example:
let navigationExtras: NavigationExtras = {
queryParams: {
"firstname": "Nic",
"lastname": "Raboy"
}
};
this.router.navigate(["page2"], navigationExtras);
I'm facing an issue on how to implement route restrictions based on remote data gotten from the server.
Suppose I have the following config file:
angular.module('myApp')
.config(['$stateProvider', function($stateProvider) {
$stateProvider
.state('post', {
url: '/post/:post_id',
abstract: true,
[...]
})
.state('post.view', {
url: '/view'
[...]
})
.state('post.edit', {
url: '/edit'
[...]
})
}]);
The requirements for my application are:
A post has an owner (creator of the post) and its domain may be public or private.
If the domain is public, every user will be able to see the post (entering state post.view), and, if not (domain is private), only the owner will be able to see it.
The post.edit state is only accessible to the owner.
To do this, what is the best approach?
I was thinking of having a resolve promise that fetches the data from the server (post's domain and correspondent user role), performs the required checks and returns accordingly (promise resolved or rejected).
But then, how would I redirect the user to a correct state if he is not authorized? For example, a common user trying to access the post.edit state should be redirected to the post.view state, if the post's domain is public... But if the post's domain is private, an unauthorized access page should be presented. Is it a good approach to do this directly on the resolve? What are the alternatives?
Have a look at this great tutorial on routing authorization based on roles:
role based authorization
Also have a look at this stackoverflow answer, here there is conditional routing based on login, you can use the same logic of roles for your purpose,
Ui-router, routes based on permission
Like this question, I want to dynamically add ui-router states, but I'm not sure how to do it given the following:
We start with a small set of routes (max 5) that allow the user to navigate the login process: The user logs in (multi step login process), then selects the product type and role they want to use (assuming user has more than one product type/role, else they will bypass this view). At that point, I want to go out to my service and get the list of routes the user has access to, given the userid, product type, & role - I plan to send down an array of data structures that very closely match what is provided to $stateProvider.state(...).
In my app.run.js, I am checking $rootScope.$on("$stateChangeStart" and moving the user along from view to view in the multi-step login process and therefore know when I need to go out to my service to load up the available routes.
When the routes are loaded, I'll probably put a flag in a cookie to indicate that it was properly loaded for this user/product/role.
I don't think a provider really makes sense here since I don't want the routes loaded at config. It feels wrong (not sure why) to call the service to load the routes in the stateChangeStart.
Is this approach a reasonable one?
Note: I also see that there is an outstanding request to be able to remove states from $stateProvider. In the meantime until this request is implemented, how do we clear the $stateProvider of routes (apart from a browser refresh)?
For adding states after the config phase, you should use the Future State functionality in the ui-router-extra package.
Since there's no official support for deleting routes, you could probably merge in this PR to get something going, but you'd have to add functionality to remove the condition from urlRouterProvider as well since that's a bug with that PR.
I am working on a very basic SPA using Backbone.js. My app has few routes. Among them there are 2 that give me issues: the index route ("/#index") and menu route ("/#mainmenu").
A simple workflow in my app is as follows: the user fills a form -> clicks to login -> trigger ajax request -> if login successful go to "/#mainmenu" route. if login failed, remain on "/#index" route.
On "/#mainmenu" if the user clicks on logout -> ajax request -> if logout success go to "/#index". if logout failed remain on "/#mainmenu".
The issues that I am struggling with are:
A clean way to trigger transition to "/#mainmenu" after successful login (I currently use router.navigate("mainmenu", {trigger: true}); but read that should avoid using this approach, in derrick bailey's article https://lostechies.com/derickbailey/2011/08/28/dont-execute-a-backbone-js-route-handler-from-your-code/ )
A clean way to prevent the user to go back to the "/#index" when pressing Back button in the browser from "/#mainmenu" route. I will also would like to preserve the url hash to reflect the current view.
Prevent the user to go forward to "/#mainmenu" after successful logout.
Is that even possible to prevent url hash change when clicking browsers back/forward buttons?
When I say "clean" I refer to "what are the best practices?". I partially solved some issues by saving url hashes and restore the appropriate hash (by router.navigate(currentRoute, {replace: true}); ) but I feel that it's a hacky approach.
Any feedback is welcome and much appreciated.
One way to solve this problem is by applying an async before filter on the routes that require an auth status check before the actual callback route is executed.
For example:
https://github.com/fantactuka/backbone-route-filter
The philosophy of avoiding {trigger: true} is based on the fact that when the router gets triggered with this flag, the entire initialization procedure for that route gets triggered. You will lose the benefit of having previously defined appstates because the app will have to re-initialize all content while this work had alrady been done before.
In practice, I think that it is useful to assess what your web app actually does. If losing appstate isn't an issue because the views you want to render are entirely new, then I don't see a problem with creating a client side redirect that re-inintializes your app.
If, on the other hand, your app has many views already rendered for which you want to maintain the same state as before, you can listen for an auth state event on each component that requires it, and make only those views re-render accordingly if they need to.
I don't think there's anything wrong with triggering routes, have been doing this without any issue for 2+ years. It all boils down to your requirements, read the article looks like a lot of work to me.
There are multiple ways to do this. First, you can disable back/forward buttons using window.history.forward(). Second, my favourite, is to do the processing in Router#execute. A sample might look like :
execute: function(callback, args, name) {
if (!loggedIn) {
goToLogin();
return false; //the privileged route won't trigger
}
if (callback) callback.apply(this, args);
}
On the documentation for routing at emberjs.com, it states
Is the user currently logged in? Are they an admin user? What post are they looking at? Is the settings screen open? Are they editing the current post?
In Ember.js, each of the possible states in your application is represented by a URL.
I can't seem to understand: How is it supposed to happen to have every possible of the above states is represented by a URL?
Explanation:
As far as i can see, the Ember Router is a strictly hierarchical tree structure. That makes perfect sense to me for straightforward URLs, e.g.
user/:user_id/posts/:post_id/comments
which is the locator for the comments of a single post.
Now, where would the login state of the user go? Wouldn't that create multiple URLS for the same resource, or does that not matter?
If your settings screen is a modal that can be accessed from anywhere on the site, how would that be reflected in the url?
I'm not asking for a workaround solution, but just wanted to get some opinions of how this is actually meant in the guides or what the best practises are.
Great question Conrad.
I'll offer some experience from the multiple projects I've done in Ember.
Rarely do I add the user to the route, there's a good chance you aren't going to be sending down anything but the current logged in user (unless of course you're building an admin resource). So defining the user id in the url is probably incorrect, since it doesn't define the current page, it defines the user who was viewing that current page.
The login state would be unrelated to the other resource routes. It would live at the root, and after login you would redirect to an authorized route. In the event that someone navigates directly to an authorized route, there are some good patterns for pausing that transition, navigating to the login route, then upon a valid login redirecting to the authorized route.
Generally you have multiple routes for a single resource.
App.Router.map(function() {
this.resource('login');
this.resource('post', { path: '/post/:post_id' }, function() {
this.route('edit');
this.route('someViewWithTheSameResource');
this.route('someViewWithTheSameResource2');
this.resource('comments', function() {
this.route('new');
this.route('update');
});
});
});
The grand-daddy issue is modals. Originally that was just a don't handle it in the url, there is no nice way to handle this without having to add a million routes under different settings. With the upcoming change of query params you can modify settings at a root level without having to muck up the entire resource tree. This is still in beta, so it may not quite work as expected, but the goal is to be able to handle such a use case.
http://emberjs.com/guides/routing/query-params/