Custom MVC Attributes #wdwdnet

The native ASP.Net Membership provider has attributes that we can use to decorate Actions and set permissions. But you may want to extend this security so this article will describe how you can do that.
Let's say we have an administrative Controller called UsersController. In this controller, there is an Action called Index() which lists all the users in the system.
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Web.Mvc;  
  5. using System.Web.Script.Serialization;  
  6.   
  7. namespace MVCApp.Controllers  
  8. {  
  9. public class UsersController : Controller  
  10. {  
  11. private readonly IViewModelUserBuilder _builder;  
  12.   
  13. public UsersController(IViewModelUserBuilder builder)  
  14. {  
  15. _builder = builder;  
  16. }  
  17. //  
  18. // GET: /Admin/Users/  
  19. public ActionResult Index()  
  20. {  
  21. var model = _builder.GetAllUsers();  
  22. return View(model);  
  23. }  
  24. }  
  25. }  
You might think this is fine, but think again. What happens if someone directly types into their browser <a href="#">http://mywebsite/users</a>? They will get to see this view and all the users that are returned from the database in your action. Not good.
What you should do is decorate your action with the [Authorize] annotation. This will ensure that the user is authenticated before they can access this view.
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Web.Mvc;  
  5. using System.Web.Script.Serialization;  
  6.   
  7. namespace MVCApp.Controllers  
  8. {  
  9. [Authorize]  
  10. public class UsersController : Controller  
  11. {  
  12. private readonly IViewModelUserBuilder _builder;  
  13.   
  14. public UsersController(IViewModelUserBuilder builder)  
  15. {  
  16. _builder = builder;  
  17. }  
  18. //  
  19. // GET: /Admin/Users/  
  20. public ActionResult Index()  
  21. {  
  22. var model = _builder.GetAllUsers();  
  23. return View(model);  
  24. }  
  25. }  
  26. }  
But this still does not guarantee security. You may only want Administrators to see this list of users. If you are using the built-in .Net security model, can you extend the [Authorize] annotation to include security group names. The code below will only allows users in the "Administrators" group to see the content of this view.
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Web.Mvc;  
  5. using System.Web.Script.Serialization;  
  6.   
  7. namespace MVCApp.Controllers  
  8. {  
  9. [Authorize("Administrator")]  
  10. public class UsersController : Controller  
  11. {  
  12. private readonly IViewModelUserBuilder _builder;  
  13.   
  14. public UsersController(IViewModelUserBuilder builder)  
  15. {  
  16. _builder = builder;  
  17. }  
  18. //  
  19. // GET: /Admin/Users/  
  20. public ActionResult Index()  
  21. {  
  22. var model = _builder.GetAllUsers();  
  23. return View(model);  
  24. }  
  25. }  
  26. }  
 But this requires that you use the built-in security model and I (generally) do not. I have my own builders and Membership methods that I use instead (I will share these methods with you if you want. Just let me know via the comments below). Instead, I customised the Authorize attribute to suit MY data store and builders.
Firstly, when my users log in, I have a Membership class that populates the session with (amongst other things) a list of the user's roles.
  1. //Store the user roles in session  
  2. var roles = new List<ViewModelRole>();  
  3. var vm = _authBusiness.GetRolesByUserName(userName).ToList();  
  4. roles = Mapper.Map(vm, roles);  
  5.   
  6. HttpContext.Current.Session[Constants.SESSION_USER_ROLES] = roles;  
Now I can use these roles at any time in my application without having to hit the database again. The downside is that the list can become "stale". That is, if the user's roles change, the user needs to log off and back on again for those changes to be visible. A small price to pay, IMHO.
So now we have all the roles for the currently logged in user in session.
Next thing to do is override the AuthorizeAttribute class with our own code. We just create a new class something like this.
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Net;  
  5. using System.Web;  
  6. using System.Web.Mvc;  
  7.   
  8. namespace MVCApp.Authentication  
  9. {  
  10. public class CustomAuthorize : AuthorizeAttribute  
  11. {  
  12. //  CustomAuthorize will be the syntax we use as decoration on our Actions  
  13. private readonly string[] _allowedroles;  
  14. public CustomAuthorize(params string[] roles)  
  15. {  
  16. //  this is the comma-delimited list of roles passed from the Authorize attribute  
  17. _allowedroles = roles;  
  18. }  
  19.   
  20. protected override bool AuthorizeCore(HttpContextBase httpContext)  
  21. {  
  22. //  get the list of roles that we already have in session  
  23. List<ViewModelRole> roles = (HttpContext.Current.Session["SESSION_USER_ROLES"]) as List<ViewModelRole>;  
  24. //  if there are none, we already fail so true immediately  
  25. if (roles == null || !roles.Any())  
  26. return false;  
  27.   
  28. //  looks for matches between the list of roles in session and the list of roles passed from the Authorize attribute  
  29. return _allowedroles.Select(role => roles.Where(x => x.Name == role)).Any(matchedRoles => matchedRoles.Any());  
  30. }  
  31.   
  32. protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)  
  33. {  
  34. if (!filterContext.HttpContext.User.Identity.IsAuthenticated)  
  35. {  
  36. //  is the user authenticated?  
  37. base.HandleUnauthorizedRequest(filterContext);  
  38. }  
  39. else  
  40. {  
  41. //  use is authenticated so throw an exception  
  42. throw new HttpException((Int32)HttpStatusCode.Forbidden, "You do not have access to this page. Please contact your administrator.");  
  43. }  
  44. }  
  45. }  
  46. }  
Check the comments in the code for some more description, but we do is
  • Pass in a comma-delimited list of roles
  • Get the list of roles already stored in session
  • Compare the two lists and find a match
And then our Action decoration looks like this.
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Web.Mvc;  
  5. using System.Web.Script.Serialization;  
  6.   
  7. namespace MVCApp.Controllers  
  8. {  
  9. [CustomAuthorize("Administrator")]  
  10. public class UsersController : Controller  
  11. {  
  12. private readonly IViewModelUserBuilder _builder;  
  13.   
  14. public UsersController(IViewModelUserBuilder builder)  
  15. {  
  16. _builder = builder;  
  17. }  
  18. //  
  19. // GET: /Admin/Users/  
  20. // Alternatively, you can set the decoration on the action  
  21. // Just remember though, the controller decoration will take precedence over the action  
  22. [CustomAuthorize("Administrator")]  
  23. public ActionResult Index()  
  24. {  
  25. var model = _builder.GetAllUsers();  
  26. return View(model);  
  27. }  
  28. }  
  29. }  
Let me know if you would like to know any more about my Membership provider.
Til next time ...

Up Next
    Ebook Download
    View all
    Learn
    View all