Nov 17, 2009

MVC Custom authorization (part 2)

MVC Forms Authentication and Authorization (membership and custom implementation) part 2

In my
previous article i discussed how to do custom forms authentication with MVC and in short reviewed the membership authentication that comes out-of-the-box. Refer to part 1 for details.

Today i will focus mostly on the authorization. I will reuse the database model exposed in part 1 and i will demonstrate how to build a custom authorize attribute which will server our needs for giving more granular restriction to protect our action methods.

Let's get back to part 1 and to reconsider the way we have implemented authentication process. Does it worth all the bunch of code we developed only for the sake of authenticating the user. Considering that MVC supports this out-of-the-box with membership, I would say – it is up to you to decide. For me – not, period. Still it mostly depends on if you are starting your development from the scratch, or you are faced with integration with developed code.

After we configured and secured our MVC application, we can protect our controllers and our action methods by specifying users and roles who can access the methods. To implement this, we can use either IAuthorizaiton attribute, or its implementation in MVC – Authorize attribute.
The Authorize attribute provides two parameters that can be both used and applied in the same time
• Users - comma separated list of users that are granted with access. Username could be Domain\Username too.
• Roles – comma separated list of roles that a granted with access. It could be Domain\Group from Active Directory.
• No specified parameters – user should be authenticated but he/she can access controller and action methods regardless of his username and role given


If I apply the Authorize attribute to an action method or controller and the user is not authenticated, he will be redirected to the login page on trying to execute the method. The same will happen if the user doesn’t meet values mentioned in Roles and Users parameters if they are specified for the given Authorize attribute.

But, what if we want from now on to build our authorization based on these granular access rights that our database is designed for? In this case our approach overdo the membership, just because it is custom, flexible and extensible the way everybody wants it.

Authorization is pretty much what user can do. One side of this, we can say is what user sees. Let’s make our sample a bit more complicated.
In table Modules we have 3 records – “Administration”, “Importing”, “Invoice”. And we have function rights – “Access”, “Manage Users”, “Manage Invoices”, “Generate Reports” in Functions table. In ModulesFunctions we bind our functions rights to appropriate modules.

Let’s make authenticated users to see only modules which they have been granted access to.

For the sake of the demo I have created user kbochevski who has “Access rights” to “Administration” and “Import module”. I use custom authentication model built in part 1. Also this user has all the other rights that are related to the mentioned modules. All this is configured and hardcoded in “AccessToModuleFunctions” table in database.
Let’s keep the modules and functions records as enums in our code (They are nomenclatures and they are pretty much read only, so this is a good practice. If you choose this approach don’t forget to sync them up with your database changes). Important thing is to put the attribute Flags of our Functions enumeration, since we want its values to be properly treated when doing bit operations against its elements. But we will get back on this later…(Check how our custom attribute has been applied in the given example later)



We will fetch our module access from the database and will store this information in cookie, which we’ll be checked in aspx and based on its value only the appropriate modules will be displayed.

In master page of our application we will iterate through cookie values and will display only elements that we should do.



Since the only way to transport data from server side to client side (aspx) is via ViewDate (and of course TempData), it should be initiated before passing content to the view render engine. That is why the most reasonable solution is to create a base controller class that will do this for us. All controller classes MUST inherit it.



Now, at this particular step we are ready to check what we have done.



You can see, our user sees only the menus (modules) which he has access to. But I would say – don’t rely on this mechanism for restricting access rights. Another user can easily see the url and paste in address bar, and he will be able to see the content he is not supposed to.

The other side (real one) of authorization, literally pertains to what are the restrictions forced to the authenticated users. And this restriction is forced programatically.This pretty much guarantees that nobody will access things that he is not supposed to.

In our case we need analog to the Authorize attribute that comes with MVC out-of-the-box. There are two possible solutions :
1) to implement IAuthorizationAttribute
2) to inherit its implementation class in MVC AuthorizeAttribute

Authorize attribute guarantees us that only authenticated users will be able to execute certain controller methods marked with this attribute.



This approach will work. But is it what we really needed? No. We are pretty much restricted with enumerating Roles and Users. And that is.We need a mechanism which to validate users’ rights based on they access to modules with a specific functions access – manage users, do invoicing….
That is why we do need a custom attribute that should do pretty much the same as Authorize attribute.




In this class we should override the OnAuthorization attribute method. In its implementation we need to do next things:

• check if user is authenticated
• check against database if he has the set rights in database that correspond to the rights set in attirbute on calling
• better once you get the rights save them in session, rather than requesting the database every time

Here is how our attribute applies to action methods:


Let’s see if this works…


Yes it is. I can access my action method which renders desired view.

Now let’s restrain the access. Just modify the database and revoke the access right to this function.



And here is the result:


User obviously doesn't have access right. We can do it more complicated and redirect to specific page, but for the sake of the demo this is quite enough.

You can refer my new article for designing MVC.NET 3 applications, which contains the custom attribute authorization in its code sample here.

To download the source code of the example i built refer:
source code
Enjoy!!!

Well, we reviewed how to do authentication and authorization in MVC, we reviewed what comes with MVC out of the box.
You can mix both approaches whenever you need it.

18 comments:

  1. That is really cool, thank you, thank you.

    ReplyDelete
  2. Great article Kaloyan!

    ReplyDelete
  3. Wow. Great help. Thanks!

    ReplyDelete
  4. I couldn't find any source code on the link mentioned here

    ReplyDelete
  5. Go to the "Source" tab. You can check out read-only copy which should be quite enough

    ReplyDelete
  6. genius, thanks

    ReplyDelete
  7. It's marvelous and fabulous mate.....

    ReplyDelete
  8. Fantastic post, just keep scratching my head with one question and can't figure it out. If I give a user access to the Administration section, but remove their ability to modify users, how can I hide the modify link from showing?

    Thanks!

    ReplyDelete
  9. I have implemented your solution in one of my projects, but I have an issue with it.

    When user A logs in, it works fine, but when user B logs in - the session of user A is changed to that of user B.

    In other words, when a user logs in, it overwrites the session of all other users logged in.

    Have I missed something somewhere - are you using server side cookies, server side sessions or something, that would typically produce this kind of behaviour...?

    ReplyDelete
  10. Or perhaps my IIS is misconfigured??

    ReplyDelete
  11. I have solved it... Damn... Note to self: DON'T USE STATIC VARS FOR ACCESSING LOGGED IN USER...!!!

    ReplyDelete
  12. http://mvc-authorization-authentication.googlecode.com/svn/trunk/ mvc-authorization-authentication-read-only

    Why there is no ZIP file? you have to download single files? I dont have a svn code on my box.

    Thanks

    ReplyDelete
    Replies
    1. to download the source code you have to use svn client and checkout this url
      http://mvc-authorization-authentication.googlecode.com/svn/trunk/

      Delete
  13. how do you create the passwords in the table? how do I use the HASH feature or skipt it?
    I just want to test this solution but it wont work for me. -- thank you

    ReplyDelete
    Replies
    1. The password is created using HASH feature. There is a text file, containing sample logon name and password

      Delete
    2. Cann't find the text file, what is sample logon name and password ?

      Thx

      Delete
  14. source code DL is not available

    ReplyDelete
  15. I can't find the source code in the links. :(

    ReplyDelete