Geeks With Blogs

@tyarmer
  • tyarmer Hmmmm... chuckled listening to NPR this morning and hearing someone say something isn't "Rocket Scientry" about 561 days ago
  • tyarmer So pretty sure the phrase "filter out the dicks" was used 6 or 7 times in less than two minutes... #AwesomeLNK about 606 days ago
  • tyarmer Am I connected to the most unstable power node or something? about 679 days ago
  • tyarmer It's awfully late and awfully cold to have the power go out! Hope this doesn't last long. about 680 days ago
  • tyarmer I am so ready to go home. Its been a long week. about 685 days ago
  • tyarmer Finally home. I love deployment nights. Time to catch a little sleep. about 686 days ago
  • tyarmer Well... It seems I may be the owner of a windows surface now... about 689 days ago
  • tyarmer How many people honestly buy Mercedes and BMW cars for Christmas? about 694 days ago
  • tyarmer @tysonj Nope. The ranger died earlier this year. Now driving a 95 F250 lol. about 694 days ago

News

Thomas Yarmer's Blog Another development blog...
| Home |

Recently I was working on a project that had a large amount of roles that were going to be utilized on many different controllers and even on individual controller actions. Originally it was given to me utilizing the standard out-of-the-box way of Authorizing with MVC 1.0:

//MVC’s standard authorize attribute
[Authorize(Roles=”Administrator, User”)]
public class HomeController : BaseController
{
	...
}

I was told that role changes may occur in the future. This caused fear of the amount of work that would be required to add or edit roles if there are currently 20 to 30 roles and that each individual controller and sometimes individual actions were using the authorize attribute. Not to mention how easy it would be to miss one of the Roles strings in one of the authorize calls.  This lead me to want to find a way to make the roles strongly typed. The first attempt I tried (proposed to me by Ryan Ohs) was to set a static class with a method to join strings back into the required format:

public static class ProjectRoles
{
	public static string List(params string[] roles) 
	{ 
      		return string.Join(", ", roles); 
	}
	public static string Admin = “Admin”;
	public static string Support = “Support”;
	public static string User = “User”;
	//and all roles continue
}

With that class the call to MVC’s authorize would be:

[Authorize(Roles=ProjectRoles.List(ProjectRoles.Admin, ProjectRoles.Support))] 
public class HomeController : BaseController 
{ 
}

A great looking solution that allowed me to change roles without having to go and change every single controller. Unfortunately, since attribute values must be defined at compile time and not at run time this solution wouldn’t work. So while continuing to try and find a method to strongly type the roles I came across a solution to support multiple roles with enums. This solution was a bit unsightly too me since it required a bitwise OR anytime you wanted to use multiple roles. Finally, I decided to change the ProjectRoles into a series of constants to represent the individual roles and to create a custom authorize attribute. I wanted to accomplish two goals with the custom authorize attribute: first I wanted to redirect a failed authorization to somewhere other than the logon page, and second I wanted the authorize attribute to allow me to use my ProjectRole constants. To accomplish this I had to override two of MVC’s authorize attribute methods, OnAuthorization and AuthorizeCore:

public static class ProjectRoles 
{
	//now constants for the attribute values
	public const string Admin = “Admin”; 
	public const string Support = “Support”; 
	public const string User = “User”; 
	public const string Guest = “Guest”;
	//and roles continue
}

public class CustomAuthorize : AuthorizeAttribute
{
	//Property to allow array instead of single string.
	private string[] _authorizedRoles;
	public string[] AuthorizedRoles
	{
		get { return _authorizedRoles ?? new string[0]; }
		set { _authorizedRoles = value; } 
	}
	public override void OnAuthorization(AuthorizationContext filterContext)
	{
		base.OnAuthorization(filterContext); 
		
		//If its an unauthorized/timed out ajax request go to top window and redirect to logon.
		if (filterContext.Result is HttpUnauthorizedResult && filterContext.HttpContext.Request.IsAjaxRequest())
                	filterContext.Result = new JavaScriptResult() { Script = top.location = '/Account/LogOn?Expired=1'; };

		//If authorization results in HttpUnauthorizedResult, redirect to error page instead of Logon page.
		if(filterContext.Result is HttpUnauthorizedResult)
			filterContext.Result = new RedirectResult("~/Error/Authorization");
	}
	protected override bool AuthorizeCore(HttpContextBase httpContext)
	{
		if (httpContext == null)
			throw new ArgumentNullException("httpContext");

		if (!httpContext.User.Identity.IsAuthenticated)
			return false;

		//Bypass role check if user is Admin, prevents having to add Admin role across the whole project.
		if(httpContext.User.IsInRole("Admin"))
			return true;

		//If no roles are supplied to the attribute just check that the user is logged in.
		if(AuthorizedRoles.Length==0)
			return true;

		//Check to see if any of the authorized roles fits into any assigned roles only if roles have been supplied.
		if (AuthorizedRoles.Any(httpContext.User.IsInRole))
			return true;

		return false;
	}
}

Side Note: I do know you can utilize OnAuthorizedFailed() in MVC 2.0, but unfortunately this project was built using MVC 1.0 and not the most recent version.

This solution had an additional benefit besides my two original goals in that any user in the Admin role will always be authorized without having to check for it. The main accomplishment was being able to utilize the string constants in ProjectRoles:

[CustomAuthorize(Roles= new []{ProjectRoles.Support, ProjectRoles.User})]
public class HomeController : BaseController
{
}

This allows any admin, support, or user access to the HomeController, and although I really would rather not have to declare the new array, it seems I will have to live with it due to the restrictions on attributes in C#.

The other issue that came up with utilizing strongly typed roles in MVC is using them in custom controls and/or restricting partial views, such as only allowing certain menu items to populate based upon the user’s role. Not wanting my code to look something like this:

<% var acceptedRoles = new string[] {ProjectRoles.Admin, ProjectRoles.User};
if(acceptedRoles.Any(HttpContext.Current.User.IsInRole)) { %>
<%=Html.ActionLink(“Home”, “Home”, “Home”)%>
<%}%>

I decided to create an extension for the IPrincipal class to be able to check if the current user was in any of the roles listed from ProjectRoles. This made the code for the menu become much more readable:

public static class IPrincipalExtend
{
	public static bool HasAnyRole(this IPrincipal user, params string[] roles)
	{
		return roles.Any(user.IsInRole);
	}
}

 

<% var currentUser = HttpContext.Current.User; %>
<% if (currentUser.HasAnyRole(ProjectRoles.Admin,
		              ProjectRoles.Support, 
			      ProjectRoles.User))
{%>
	<%=Html.ActionLink("Home", "Home", "Home")%>
<%}%>
<% if (currentUser.HasAnyRole(ProjectRoles.Admin,
			      ProjectRoles.Support, 
			      ProjectRoles.Guest
			      ProjectRoles.User))
{%>
	<%= Html.ActionLink("GuestHome", "GuestHome", "Home") %>
<% }%>

And although this will be great when I want to restrict viewing a piece of the View every now and then. I did realize that primarily I will be wanting to restrict access to ActionLinks. And not too mention if I hid them completely that some of the eixsting tables and displays might become skewed depending on what was and wasn't rendered. So I decided to go ahead and write an extension for the HtmlHelper as well:

public static string ActionLinkWithRoles(this HtmlHelper html, string linkText, string actionName, string controllerName, params string[] roles) 
{
	if (HttpContext.Current.User.HasAnyRole(roles)) 
        	return html.ActionLink(linkText, actionName, controllerName); 
    
	return linkText;
}

Now for the most common restriction that needs to be placed simply became:

<%=Html.ActionLinkWithRoles("Home", "Index", "Home", ProjectRoles.Admin, ProjectRoles.User)%>

And that concluded my fight for implementing strongly typed roles in an existing MVC project.

-Tom

Posted on Thursday, February 25, 2010 5:52 PM | Back to top


Comments on this post: Strongly Typed Roles in MVC with Authorize Attribute

# re: Strongly Typed Roles in MVC with Authorize Attribute
Requesting Gravatar...
I would recommend extracting the security logic (cross cutting concern!) from the view itself. The view should be able to obtain everything it needs to be rendered from the View Model because that is the view's contract with the application for what information will be provided.

<%= if(viewModel.UserCanAccessGuestHome) { Html.ActionLink("GuestHome", "Index"); } %>

Also, instead of an extension method you could wrap up the IPrincipal with a security service that does these checks for you. Just a bit cleaner API in my opinion.
Left by Ryan on Feb 25, 2010 8:45 PM

# re: Strongly Typed Roles in MVC with Authorize Attribute
Requesting Gravatar...
@Ryan there are still plenty of things I would like to do with the security in the application. Unfortunately, it is a legacy application, one I'm not entirely familiar with yet, and the time cost is a strong consideration.
Left by Thomas Yarmer on Feb 25, 2010 9:27 PM

# re: Strongly Typed Roles in MVC with Authorize Attribute
Requesting Gravatar...
Were you able to extract the security logic from the view?
Left by Brazilian Hair Removal Cream on Sep 11, 2010 11:06 PM

# re: Strongly Typed Roles in MVC with Authorize Attribute
Requesting Gravatar...
heloo im flora
im programmer
i want to thank you for this comment
it resolve my problem
Left by flora on Oct 03, 2010 3:37 AM

# re: Strongly Typed Roles in MVC with Authorize Attribute
Requesting Gravatar...
I recently had a similar need, but I took a different approach. I made an enum with all the roles, then attached to it a T4 template that generates a static class with string constants, one for each value in the enum. This solution had all the pros with no cons, at least to me.
Left by Matteo on Feb 07, 2011 2:08 AM

# re: Strongly Typed Roles in MVC with Authorize Attribute
Requesting Gravatar...
Hi, Thanks for the write-up.

For me, the solution of deriving a new Auth. attribute from the existing, and overriding the auth method was perfect. As for the other stuff, meh. (Ha).

It was very helpful to me. Thanks.

I used enums, so my final attribute implementation looks nice and tidy.....
(changed the company name)


[MyCoolNewAuthorization(
AuthorizedRoles = new []
{
MyCoolNewMemberRoles.Partner,
MyCoolNewMemberRoles.SalesPerson,
MyCoolNewMemberRoles.Admin
})]


Non-related plug... I make video games, check mine out!
Left by Mike Tsouris on Mar 25, 2011 9:14 AM

# re: Strongly Typed Roles in MVC with Authorize Attribute
Requesting Gravatar...
내가 찾던 역할 및 권한 관련 자료
Left by 김재훈 on Mar 25, 2011 10:50 AM

# re: Strongly Typed Roles in MVC with Authorize Attribute
Requesting Gravatar...
Do you have source code about this? It was design by MVC 1, so it can be suitable in MVC 2?
Left by Character_kute on Jun 13, 2011 8:06 AM

# re: Strongly Typed Roles in MVC with Authorize Attribute
Requesting Gravatar...
Tom,
good article. I want chat with regarding authorize attribute and using it in MVC 3 for app security and access builder. Sal
Left by Sal Alam on Jun 21, 2011 9:54 PM

# re: Strongly Typed Roles in MVC with Authorize Attribute
Requesting Gravatar...
I did a variation of you solution to allow for a role hierarchy. I posted it here to get some feedback:

http://stackoverflow.com/questions/6476157/role-hierarchy-in-mvc-with-strongly-typed-roles

Thanks for your post, it helped alot!
Left by Phillip Jones on Jun 25, 2011 12:57 AM

# re: Strongly Typed Roles in MVC with Authorize Attribute
Requesting Gravatar...
In the overridden attribute you refer to top.location.

This does not compile.
Left by Jon on Oct 11, 2011 3:24 PM

# re: Strongly Typed Roles in MVC with Authorize Attribute
Requesting Gravatar...
Can you specify a different filterContext.Result if they are not logged in so you can send the user to the login page if not logged in or to an error page for another reason?
Left by Jon on Oct 12, 2011 5:44 AM

# re: Strongly Typed Roles in MVC with Authorize Attribute
Requesting Gravatar...
I get the following error:
Cannot implicitly convert type 'string[]' to 'string'

How do I allow the new class to accept string[] instead of string???
Left by Charles on Oct 12, 2011 6:24 PM

# re: Strongly Typed Roles in MVC with Authorize Attribute
Requesting Gravatar...
"In the overridden attribute you refer to top.location."

I believe his RTE parsed out a couple of quotes. This fixes the line, and compiles.

filterContext.Result = new JavaScriptResult() { Script = "top.Location = '/Account/LogOn?Expired=1';" };
Left by Jamal on Jan 27, 2012 3:29 PM

# re: Strongly Typed Roles in MVC with Authorize Attribute
Requesting Gravatar...
Thanks a lot for the post... It really helped me a lot in applying authorization to the application.... Great job Thomas... keep posting :)
Left by Madhura on Apr 06, 2013 10:16 PM

Your comment:
 (will show your gravatar)
 


Copyright © Thomas Yarmer | Powered by: GeeksWithBlogs.net | Join free