Question

I am trying to create a permissions system in an ASP.NET MVC Application. I have been learning the newest Identity framework - here are my requirements:

  1. A set of Hierarchical Roles for each set of functionality. So, for instance, there might be the following roles:
    • Inherit
    • Reader
    • Editor
    • Manager
    • Administrator
  2. Each user would have one of those roles for each module (e.g. Events, Pages, etc.)
  3. Users can be members of a security group. Each security group can be assigned a role directly and then all users in that group (who have not been explicitly assigned that permission) will inherit that role.
  4. Multi-tenant site: each user has a set of sites which they are a member of. In the context of each site, they have a complete set of permissions which can be assigned by the site admin.

Through extending ASP.NET Identity, is it going to be possible for me to accomplish all of this? Or should I be building something custom from the ground-up?

Was it helpful?

Solution

9,999 times out of 10,000 implementing your own authentication system is the wrong way to go. Anything is easier than that, and it's a deceptively difficult thing to do right. ASP.NET Identity is actually pretty customizable, as it was created specifically for that purpose. You might need to do quite a bit to bootstrap your custom requirements fully, but it'll almost certainly be quicker and more secure using ASP.NET Identity.

UPDATE

UserManager's constructor takes an implementation of IUserStore. When working with Entity Framework, you typically just feed it Microsoft.AspNet.Identity.EntityFramework.UserStore, but this is your tie in point for extensibility. So, you can simply subclass UserStore and then override something like GetRolesAsync to do whatever custom role logic you need to implement. Then you'd just feed UserManager your subclass.

OTHER TIPS

In version 1.0 of ASP.NET Membership, the IRole interface must have a string primary key. However in version 2.0, released March 2014, they added an IRole<TKey> that allows you to specify a role's primary key, as long as TKey implements IEquatable<TKey>.

That said, out of the box MVC integration points for authorization still depend on roles being ID'd by a single string. So if you are going to do authorization there, via attributes like Authorize, then you may need to write your own custom authorization attributes.

One way to achieve hierarchical roles would be to handle it in your application instead of in the model. I assume by hierarchical, you mean that Administrators have all the privileges of Managers, Managers have all the same privileges as Editors, and so on. You could achieve this by adding users to multiple roles, instead of having to walk through a modeled role hierarchy. Something like a db trigger could do it, but you can model it as a business rule in code too. Then if you restrict a page to Editor, Admins & Mgrs would have access to it as well.

Another way would be to just authorize certain actions for multiple roles:

[Authorize(Roles = "Administrator, Manager, Editor")]
public ActionResult Edit()

[Authorize(Roles = "Administrator, Manager")]
public ActionResult Manage()

[Authorize(Roles = "Administrator")]
public ActionResult Admin()

I disagree though that you would want to id roles on a composite key. If you want to protect MVC actions using the Authorize attribute, the ID of the role needs to be a constant value, like a string literal, int or Enum value, etc. If you keyed role on more than one of its properties, the number of properties you need to set on the attribute multiplies by the number of values in each component of the id. You would have Manager/SiteA, Manager/SiteB, and so on.

Instead, it sounds like it might be a good idea to just add properties to the gerund that tracks users in roles (the in-between table in a many-to-many relationship). To do this, you wouldn't be able to simply override and extend methods in the UserManager class as @Chris Pratt suggested. But that doesn't mean you have to throw out the baby with the bathwater. You can still use Microsoft.AspNet.Identity for authentication, and just write your own methods for role management, augmenting them to take an additional parameter:

AddToRoleAsync(TUser user, string roleName, string siteId);

public class Role : IRole<int>
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<UserInRole> Authorizes { get; set; }
}
public class UserInRole
{
    public int RoleId { get; set; } // part of composite primary key
    public int UserId { get; set; } // part of composite primary key
    public string SiteId { get; set; } // part of composite primary key
    public virtual Role Role { get; set; }
    public virtual User User { get; set; }

}
public class User : IUser<int>
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<UserInRole> Authorized { get; set; }
}

Given the above, say your URL's look something like this:

/sites/site-a/admin
/sites/site-b/manage
/sites/site-c/edit
/sites/{siteId}/do

...you could write a custom authorization attribute that checks the URL and authorizes the principal both against the role name in the attribute and the siteId in the URL. To get access to the db from the attribute, if you are using IoC for EntityFramework, you can property inject an instance of your DbContext (or whatever interface you have wrapping it).

Following steps can solve your problem

  1. Create granular level of roles...typically for each action
  2. Group them up into GroupRoles...so that admin can easily manage it
  3. Add individual level claims to user for specific permission

Some good examples of the same are below

Hope this solves your problem

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top