I am trying to have protected routes like these: 1). loggedin user group routes 2.) admin routes, 3.) student routes, 4.) public routes. The LoggedInUser works as expected but the 2 other routes - schooladmin and students does not work as needed.
After logging in as an admin or as a student, according to the expectation the respective users should be able to go to the allowed urls but whenever, as an example, if a schooladmin admin goes to http://localhost/students it automatically redirects back to dashboard, and likewise for student. What am I to do right?
This route group allows only logged in users.
var LoggedInUser = FlowRouter.group({
name: 'currentUser', triggersEnter: [function (context, redirect) {
if (Meteor.loggingIn() || Meteor.userId()) {
FlowRouter.watchPathChange();
let currentRoute = FlowRouter.current();
if (!currentRoute.path) {
FlowRouter.go('/dashboard');
} else {
FlowRouter.go(currentRoute.path);
}
} else {
redirect('/');
}
}]
});
This is the route group for school admins
var schooladmin = LoggedInUser.group({
name: 'schooladmins', triggersEnter: [function (context, redirect) {
FlowRouter.watchPathChange();
let currentRoute = FlowRouter.current();
if (Roles.userIsInRole(Meteor.userId(), ['super-admin', 'admin'])) {
console.log(currentRoute.path);
FlowRouter.go(currentRoute.path);
} else {
redirect('dashboard');
}
}]
});
This is the route for students
var students = LoggedInUser.group({
name: 'students', triggersEnter:[function (context, redirect) {
FlowRouter.watchPathChange();
let currentRoute = FlowRouter.current();
if (Roles.userIsInRole(Meteor.userId(), ['manage-team', 'student-page'])) {
FlowRouter.go(currentRoute.path);
} else {
redirect('dashboard');
}
}]
});
Sample routes the groups are attached to
This sample route is for school admins only to access
schooladmin.route('/students', {
name: 'students', action(){
BlazeLayout.render('formrender', {formrend: 'student'});
}
});
this route is for student to access
students.route('/student/dashboard', {
name: 'students-dashboard', action(){
BlazeLayout.render('studentlayout', {studentrender: 'studentdashboard'});
}
});
The Roles package actually depends on a subscription, which means that if the subscription is not ready the Roles.userIsInRole method will always return false. And then your route fails, because FlowRouter always runs no matter what. This happens in very specific cases, but it happens and your users will notice.
Fortunately there is now a fix. We can have full control over when FlowRouter initializes.
There are 2 ways to achieve this.
Just above FlowRouter Declaration use Accounts.onLogin(function(user){}); method to check role and then redirect. (check user._id for Roles)
Click here for second solution https://medium.com/#satyavh/using-flow-router-for-authentication-ba7bb2644f42
Related
i'm trying to redirect a user to 403 page when not authorized. I added roles 'admin', 'default-group' to CM9Cwq7HXD6yHjKRp and it's working like a charm on template level. But not working as expected on router.
My route groups are separated into 2 main group
// Public routes
var publicFlowRouter;
publicFlowRouter = FlowRouter.group({});
// Private routes
var privateFlowRouter;
privateFlowRouter = FlowRouter.group({
triggersEnter: [
function() {
var route;
if (!(Meteor.loggingIn() || Meteor.userId())) {
route = FlowRouter.current();
if (route.route.name !== 'home') {
Session.set('redirectAfterLogin', route.path);
}
return FlowRouter.go('home');
}
}
]
});
There isn't any problem for these routes but the problem starts with adminPrivateFlowRouter;
// Private routes extended for admin
var adminPrivateFlowRouter;
adminPrivateFlowRouter = privateFlowRouter.group({
triggersEnter: [
function() {
// If user is not authenticated redirect to homepage
console.log(Meteor.userId());
console.log(Roles.userIsInRole(Meteor.userId(), 'admin', 'default-group'));
if (Roles.userIsInRole(Meteor.userId(), 'admin', 'default-group')) {
console.log('Authenticated user');
} else {
console.log('403 Access Denied');
//return FlowRouter.go('home');
}
}
]
});
is not working solid. When i refresh the samepage console says sometimes
CM9Cwq7HXD6yHjKRp
false
403 Access Denied
CM9Cwq7HXD6yHjKRp
true
Authenticated user
I couldn't find where the problem is, thanks
See How to make FlowRouter wait for users collection on the client
You can solve your problem manually initializing FlowRouter with FlowRouter.wait() and FlowRouter.initialize() when Roles subscription is ready.
I am facing a strange behaviour, and can't debug it properly. I need your help.
If I log out of Meteor, everything seems fine.
If I wait for around 2 seconds and log back in again, everything is still very fine. But if I logout and quickly login again, right after the login process, the Meteor.user() object is set to null, which leads my router to redirect the user back to the login page.
Any idea why this is happening, and how could I prevent it?
I have spent 2h trying several things without success. Any suggestion is most welcome.
EDIT
This is my global onBeforeAction function :
Router.onBeforeAction(function() {
// Ensures the user is logged in
if (!Meteor.userId()) {
please_login();
}
// Email address not verified for 24h? Please verify it!
else {
var self = this;
// Waits for the user object to be passed over DDP
function wait_for_user_data() {
if (Meteor.user() && Meteor.user().emails && Meteor.user().profile) {
var user = Meteor.user();
var now = new Date().getTime();
var ca = user.createdAt.getTime();// Created At
var cs = (now - ca) / (24 * 60 * 60 * 1000);// Created Since (in days)
var urls = ["email_verification_required", "email_verification"];
if (cs > 1 &&
!user.emails[0].verified &&
urls.indexOf(self.url.split("/")[1]) == -1) {
Router.go("email_verification_required");
}
else {
self.next();
}
}
else {
setTimeout(wait_for_user_data, 500);
}
}
wait_for_user_data();
}
},
{except: ['home', 'login', 'register', 'password_recovery', "email_verification", "profile"]})
What actually happens is the following :
When I login right after having logged out, self.next() is called, but the current user properties (Meteor.user().emails and Meteor.user().profile) aren't loaded yet for some reason. They are undefined. As you can see, I tried to work around this by waiting until they are defined, but then I receive the following error message :
Route dispatch never rendered. Did you forget to call this.next()
in an onBeforeAction?
This seems to cause Meteor to set Meteor.user() to null, and so my user gets redirected to the login page...
EDIT BIS
This is how I am handling the publish/subscribe of the users data. I have 2 different pub/sub set, one for all users, and the other one for the logged in user only.
Meteor.publish('users', function() {
var args = {};
var fields = {
'_id': 1,
'createdAt': 1,
'profile.firstname' : 1,
'profile.lastname' : 1,
'profile.lang' : 1,
};
// Only admins can access those sensible information
if (this.userId && Roles.userIsInRole(this.userId, 'admin')) {
fields["emails"] = 1;
fields["profile.celular"] = 1;
args : {$ne : {_id: this.userId}};
}
return Meteor.users.find(args, {fields: fields});
});
Meteor.publish('user', function() {
return Meteor.users.find({_id: this.userId});
});
This is how I subscribe to those two publications within my router's configurations :
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
waitOn: function() {
return [Meteor.subscribe('user'), Meteor.subscribe('users')];
},
});
Without a code example (and knowing which router you're using) I'm going to take a guess that you're router code looks something like :
if (!Meteor.user()) {
//... redirect or change tempalte etc
}
There is another value you can check,
if (!Meteor.user() && !Meteor.loggingIn()) {
//... redirect or change tempalte etc
}
you can use various combinations of these to handle the login state, such as having a logging in loading view etc
With more information
The redirect is being called in a callback, currently for the login with password function, having a look at the docs it should be called on the onLogin callback linked as this ensures that login was successful, though doing the redirect here, may not provide the same range of control as doing this in a reactive context such as a before hook on a controller, or an autorun function created in the login template onCreated callback.
The way I've deal with this in the past is to define an explicit subscription to the user object then waitOn that subscription in the router. Much simpler than writing a polling loop but mostly I did it because I needed specific fields that didn't come over by default. Hopefully this can help you with your problem.
Server:
Meteor.publish('me',function(){
if ( this.userId ){
// return the fields I need
return Meteor.users.findOne({ _id: this.userId },{ fields: { field1: 1, field2: 1, ... }});
}
else this.ready();
});
Router:
waitOn: function(){
return Meteor.subscribe('me');
}
I created a collection for adminuser's enter the system.I mean I dont want to use account packet for admin side but I dont know How to make Route setting after admin to be login.I made something but it doesnt work correct,
login.html
Template.login.events({
'click #entre': function (e, template) {
var Username = template.$('#username').val();
var Password = template.$('#password').val();
var getinfo= admin.findOne({});
if (Username == " " || Password == "") {
swal("Error", "All fields must be Completed.", "error");
} else if (getinfo.username== Username && getinfo.password== Password) {
swal("Success", "Welcome admin.", "success");
Session.set("hi", true);
} else {
swal("error", "Login Informations wrong.", "error");
}
}
});
router.js
Router.onBeforeAction(function () {
if (!Session.get("hi")) {
this.render('login');
} else {
this.render('dashboard');
}
});
Router.route('userList', function () {
this.render('userList');
});
Router.route('addnewuser', function () {
this.render('addnewuser');
});
Note:I want to make that when admin to be login,it can reach to all pages userlist,addnewuser etc.
If I understood it properly you want to give admin rights to certain routes of yours. There are several ways to achieve this using iron:router, either by building a Controller or using Filters. I would create an Iron Router Controller to tackle that and attach it to any route that needs that kind of checks. Both Controllers and Filters are actually reusable bits of code which is what we are looking for.
Make sure you add alanning:roles to your packages list (if you haven't already) and add some admin roles to at least one of your Meteor.users() like it's shown here
Building the actual Controllers is easy.
lib/router.js
AdminController = RouteController.extend({
onBeforeAction: function () {
var loggedInUser = Meteor.userId();
if (!!loggedInUser) {
if (!Roles.userIsInRole(loggedInUser, 'admin')) {
// Basic redirect to the homepage
Router.go('homepage');
this.stop();
}
} else {
// Log them in when they are not
Router.go('login');
this.stop();
}
this.next();
}
});
Router.route('/admin', {
name: 'admin',
controller: AdminController,
waitOn: function () {
// return subscriptions here
}
});
Continue to add that to any other routes you like.
I want to restrict access to certain pages depending on users roles. So I don't want a logged in user to be able to just change the URL in their browser to navigate to a page they shouldn't have access to. So for such a route I'm doing something like:
action: function () {
if (!Roles.userIsInRole(Meteor.user(), 'admin')) {
this.render("AcressRestricted");
} else {
// Do routing for admin users here....
}
}
Is that the standard way to go? And do I need to add this code to every page I want to restrict or is there a more general solution / short cut?
You can use Router.onBeforeAction:
Router.onBeforeAction(function() {
if (!Roles.userIsInRole(Meteor.user(), 'admin')) {
this.render("AcressRestricted");
} else {
this.next();
}
}, {only : 'route_one', 'route_two'});
This will only work for route_one and route_two.
Be sure to name what you use in 'only' or 'except' in your route definitions:
Router.route('/' {
name: 'route_one',
...
});
You could set your code up a little differently in order to make it more readily reusable, and avoid having to copy and paste any changes across routes:
var adminFilter = function () {
if (Meteor.logginIn()) {
//Logic for if they are an admin
this.render('loading');
this.stop();
} else if (!user.admin()) {
// Logic for if they are
this.render('AcressRestricted');
this.stop();
}
};
And then whenever you need it, just drop it in along side "before:"
Router.map(function () {
this.route('adminPage', {
path: '/admin',
before: adminFilter
});
});
I've been searching about how to implement authentication/authorization in SPA's with AngularJS and ASP.NET Web API and I have one doubt. First we can implement authentication and authorization on server side with ASP.NET Identity. Then we create an Angular service to use this to authenticate a user and after that requests to Web API actions that use the Authorize attribute will be allowed.
There's still one problem. The logged in user will probably won't be allowed to access some pages of the app. Although using the app itself it won't be allowed, the HTML for the SPA is still available. If the user goes to http://website.com/app/views/notAllowedPage.html it will render in the browser. It's really not useful I know, but still I think to be a security failure, since the user shouldn't be allowed to get this HTML from the server.
Is there a way to secure this HTML or it is simply not possible?
We discussed the same problem in our developers group. Our conclusion was not to see this as a security thread.
What you want to protect is the data that is displayed, not the static "layout" of a HTML page. As long as the WebAPI services that deliver the data are secured and only allow authorized users to retrieve the data, we are safe.
Would that suit your needs as well?
We currently use this same setup.
Since we are using Angular, we don't do much with MVC itself or the Razor engine. The only things we are really doing with Razor is rendering a layout and the basic page (usually Index()).
So my recommendation is to do the same--instead of having a page website.com/app/views/notAllowedPage.html, have the user navigate to website.com/app/NotAllowed/, and secure the NotAllowedController with an Authorize attribute.
Create a service for securing your Angular htmlpage
As per the these services a guest user can't access the secure pages
Angular service
angular.module('application').factory('authorizationService', function ($resource, $q, $rootScope, $location,dataFactory) {
return {
permissionCheck: function (roleCollection) {
var deferred = $q.defer();
var parentPointer = this;
var user = dataFactory.getUsername();
var permission = dataFactory.getUserRole()
var isPermissionLoaded=dataFactory.getIsPermissionLoaded();
if (isPermissionLoaded) {
this.getPermission(permission, roleCollection, deferred);
}
else {
$location.path("/login");
}
return deferred.promise;
},
getPermission: function (permission, roleCollection, deferred) {
var ifPermissionPassed = false;
angular.forEach(roleCollection, function (i,role) {
switch (role) {
case roles.ROLE_USER:
angular.forEach(permission, function (perms) {
if (perms=="ROLE_USER") {
ifPermissionPassed = true;
}
});
break;
case roles.ROLE_ADMIN:
angular.forEach(permission, function (perms) {
if (perms=="ROLE_ADMIN") {
ifPermissionPassed = true;
}
});
break;
default:
ifPermissionPassed = false;
}
});
if (ifPermissionPassed==false) {
$location.path("/login");
$rootScope.$on('$locationChangeSuccess', function (next, current) {
deferred.resolve();
});
} else {
deferred.resolve();
}
},
isUserAuthorised: function () {
var isPermissionPassed = false;
var permission = dataFactory.getUserRole()
var isPermissionLoaded=dataFactory.getIsPermissionLoaded();
if (isPermissionLoaded) {
angular.forEach(permission, function (perms) {
if (perms=="ROLE_USER") {
ifPermissionPassed = true;
}
});
}
return isPermissionPassed;
}
};
});
and this code for your app.js where u mention your url and their controller
var application = angular.module('application', ['ngRoute']);
var roles = {
ROLE_USER: 0, //these are the roles which I want to secure from guest user
ROLE_ADMIN: 1
};
var routeForUnauthorizedAccess = '/login'; //if any unauthories user access the page the user will redirect on the login page
$routeProvider.when('/', {
templateUrl: 'view/home.html',
controller: 'homepage'
}).when('/login', {
templateUrl: 'view/loginpage.html',
controller: 'login'
}).when('/signup', {
templateUrl: 'view/signup.html',
controller: 'signup'
}).when('/dashboard', {
templateUrl: 'view/dashboard.html',
controller: 'dashboard',
resolve: {
permission: function(authorizationService, $route) {
return authorizationService.permissionCheck([roles.ROLE_USER, roles.ROLE_ADMIN]);
},
}
// the last dashboard page is access by only admin and the user who logged IN