21

I know this is a question that has been asked over and over but I'm attempting to implement permission based rather than role based authorization in an ASP.NET MVC application. So instead of just having high level roles like Manager, Admin, or User, I need to have permissions like ViewTask, AddTask, DeleteTask. I have read a ton of comments on this and it seems like the easiest solution is to just treat roles as permissions and define "roles" of ViewTask, AddTask, and DeleteTask.

Is such an approach really a good idea? Some of my concerns are that you could end up with over 100 roles depending on the size of the application which would then rule out the ability to do role caching in cookies and thus every call to User.IsInRole hits the database. If every action method is going to be decorated with [Authorize(Roles="XXXX")] am I going to see serious performance issues?

My other issue is that I still want to keep the concept of a role around so that an administrator can simply associate a user with a role that has a predefined set of permissions. Using the approach above my thought was to create a separate entity in my application named Group and that Group would be responsible for keeping track of the ASP.NET roles that are assigned to that Group. So, when a user is associated with a Group, I can retrieve the ASP.NET roles that need to be assigned to the user and add all the roles.

Has anyone implemented a system in such a way? Any opinions or thoughts on this approach would be appreciated.

Thanks

Nick Olsen
  • 6,299
  • 11
  • 53
  • 75

2 Answers2

4

I agree with @jlew about caching the user's data and when the cache expires - just reload it. There's no use trying to force this data to stay persistent. Additionally, if you want to move away from the ASP.net role providers, you could roll your own security as I've described in this reply. This has the advantage of allowing very custom security solutions for roles/individual permissions.

The following is just an idea that I've been toying around with lately (just some food for thought). Why not use the RESTful urls of MVC to define "permissions". For example:

/tasks/add could define the permission for adding tasks. These could somehow be hierarchical so that giving a user permissions on /tasks/add also gives them permissions on /tasks. Then, you could use a global action filter that would build the URL given the route values. This would also allow really interesting approach for individual item security configurable via runtime. For example, /tasks/edit/23 could somehow grant edit permissions on task with id 23. Anyway, this might not even be helpful at at all... but it's just thought I thought you'd like to maybe consider.

Cheers!

Community
  • 1
  • 1
TheCloudlessSky
  • 18,608
  • 15
  • 75
  • 116
  • I actually read your the post you linked last night and really like it because it would allow for transactional support when creating users and adding them to roles/permissions. But, everywhere I look it seems everyone says DON'T EVER ROLE YOUR OWN AUTHENTICATION. It makes sense to use a tried and true solution instead of coming up with one yourself and finding out there are security issues down the road. Thoughts? – Nick Olsen Jul 22 '11 at 20:54
  • I present the same question to you as I did to @jlew. How is that caching done? And how do you determine when the cache expires? Do make matters more interesting, we are planning on using Windows Azure for our application deployment. Would that be using the Azure AppFabric caching? – Nick Olsen Jul 22 '11 at 20:58
  • 1
    @Nick - I understand your concerns, and it's *exactly* how I felt going through the process ("Rolling your own security layer is crazy!!!"). However, I've found that it's usually related to hashing passwords and the like. It's not *really* that hard if you're careful. Use something like SHA512 for hashing the passwords, use a random salt. Also, I've personally yet to see a large "enterprise" application use the built in providers. How else are you going to use a security layer if your application is not *just* ASP.NET? You'd have to roll your own. – TheCloudlessSky Jul 22 '11 at 20:59
  • @Nick - The caching I usually do is with either the HttpSession (since it's per-user cache - and could be scaled with your own session provider) or if you're using NHibernate (or another ORM) you could utilize their caching layer. For the HttpSession, the item would be valid until the session expires. For NHibernate, it would just re-query the database all on its own (you wouldn't have to write that code). I personally haven't used Azure, so I can't comment on it's caching capabilities, sorry! – TheCloudlessSky Jul 22 '11 at 21:06
  • So what do you do when the session expires and the authentication cookie is still valid? Do you just reload everything into session again or do you require the user to log back in? – Nick Olsen Jul 22 '11 at 21:16
  • @Nick - I guess that really depends on how you'd like to implement it. In one of my most recent projects, a requirement was for force 30min non-active session windows, and therefore force the user to log back in. However, the alternative would be to see if the session is expired and the user is authenticated: reload the user's details into session. Hope this helps! – TheCloudlessSky Jul 22 '11 at 21:23
  • Great. I have a BaseController class that all my Controller's extend from. My thought would be to override the OnActionExecuting method and perform a check to see if the Session["CurrentUser"] value was present and then reload it if necessary. But, since I couldn't check User.Identity.IsAuthenticated using your approach, how would you determine if the user was authenticated? Checking the authentication cookie? How is that done? – Nick Olsen Jul 22 '11 at 21:31
  • I was mistaken, when you use the method FormsAuthentication.SetAuthCookie("username", false), that does populate the User and User.Identity objects with the given user so I could check User.Identity.IsAuthenticated. So, would it be a valid in my BaseController to do something like if (User != null && User.Identity.IsAuthenticated && HttpContext["User"] == null) // Load the User information including their permissions back into session – Nick Olsen Jul 22 '11 at 21:44
  • @Nick - I would use an action filter (if you're using MVC3 a global action filter would be best!) and decorate it on each controller. This way, your tests aren't affected in any way possible by the authentication code. Check out my post that I mentioned for more details on this. – TheCloudlessSky Jul 23 '11 at 03:37
  • Also - I didn't post the code for the `FormsAuthenticationService`, but yeah it'd be something like `if(!User.Identity.IsAuthenticated && HttpSession.IsExpired()` (though there is no `IsExpired` method...but it's easily done). If necessary I can provide some code from a project, just let me know. – TheCloudlessSky Jul 23 '11 at 03:42
  • An example project would be awesome if you were willing to share. A global action filter sounds good too and with Ninject I can easily inject my AccountService class to retrieve the user information if necessary. – Nick Olsen Jul 24 '11 at 13:59
  • @Nick - I don't have a full project that I'm allowed to share on this machine, however I did round up the code that make the security layer. You can find it [here on BitBucket](https://bitbucket.org/TheCloudlessSky/sampleauth/src). If you have any questions, let me know. Cheers! – TheCloudlessSky Jul 24 '11 at 19:48
  • I tried to access the link but got an access denied error message. – Nick Olsen Jul 25 '11 at 03:10
  • @Nick - Oops... the repo was set to private, should be good now. – TheCloudlessSky Jul 25 '11 at 10:03
  • Thanks for the information. Just one question. What does your Permissions class look like and what lends it to allow (this.Permission & userSession.Permissions) == this.Permission to function? – Nick Olsen Jul 25 '11 at 15:39
  • @Nick - Oops didn't include that. In the code I provided there, it's just an enum (which allows the bit-wise AND). The particular project didn't have a large permission set (5-10) and therefore it was much easier to implement with enums rather than something more complicated. – TheCloudlessSky Jul 25 '11 at 16:25
  • Got it. Thanks again for the example. I will get back to you if I have more questions. – Nick Olsen Jul 25 '11 at 16:26
  • Is there any benefit to storing the PasswordHash and PasswordSalt as a byte[] (varbinary) instead of converting it to a base 64 string and storing it as a varchar? – Nick Olsen Jul 26 '11 at 19:22
  • @Nick - Well, you get bytes from the hashing methods, so why not store their binary instead of adding *another* step to convert them. The hashes aren't *really strings*, it's just 1's and 0's. Therefore, that's why I leave them as `byte[]`. However, I *have* seen applications store it as varchar without any problem. Honestly, it's negligible so I'd pick which ever you're most comfortable with. – TheCloudlessSky Jul 26 '11 at 20:10
  • One last question, your RequirePermissionAttribute implements the IAuthorizationFilter instead of just extending the AuthorizationAttribute attribute and overriding the OnAuthorization method. Any reason for that other than the fact that extending the AuthorizationAttribute still lets you specify the Roles and User parameter which I wouldn't be using? – Nick Olsen Jul 27 '11 at 18:05
  • @Nick - Yeah that was pretty much the reason (I can't think off the top of my head what else). You can use your favorite decompiler (reflector, dotPeek, etc..) and compare with the actual `AuthorizationAttribute`. However, I think the main reason for doing this was to remove the unnecessary properties for roles/user. – TheCloudlessSky Jul 27 '11 at 20:29
  • Dont hash passwords with SHA(ANYTHING). http://msdn.microsoft.com/en-us/library/system.security.cryptography.rfc2898derivebytes(v=vs.100).aspx – Sam Apr 17 '13 at 06:29
2

We solve the problem by caching the principal on the server side, so that the "permission roles" do not need to be in the cookie and we do not have to re-load on every request. You can actually get around the cookie size limitation by chunking your cookie data into multiple cookies (Windows Identity Framework does this.) But, you may have bandwidth or other concerns with big cookies.

jlew
  • 10,491
  • 1
  • 35
  • 58
  • But you are using this approach as far as the roles really being permissions? I assume you load the "roles" on user login. What do you do when the session times out before the authentication cookie expires? – Nick Olsen Jul 22 '11 at 17:32
  • 1
    Yes, roles == permissions. We are not using session state, but out identity cache is only good for 5 minutes or so. When we ask for the identity, we either get back the cached instance or reload it if it has been flushed. Reloading occasionally isn't so bad. – jlew Jul 22 '11 at 18:47
  • How do you do the caching? Is the identity cached automatically for you? How do you include the permissions in the identity cache? – Nick Olsen Jul 22 '11 at 20:32
  • We have a class derived from IPrincipal which implements IsInRole and keeps a list of strings to represent the roles. It is using the ASP.NET class (HttpCache, I think?) as the cache store. – jlew Jul 22 '11 at 21:01
  • @jlew - Out of curiosity, how do you use HttpCache since it's scope is the whole application and not-per user cache (compared to HttpSession)? – TheCloudlessSky Jul 22 '11 at 21:07