ASP.NET MVC 5 Custom Role Providers for Windows Authentication

ASP.NET MVC 5 Custom Role Providers for Windows Authentication

Ben Daland
Business Analytics Specialist
John Daniel Associates, Inc.

Ben’s Profile

With ASP.net MVC, restricting access to actions has never been easier. The AuthorizeAttribute allows you to easily restrict access to controllers or actions based on a user’s role. See the example below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[Authorize(Roles = "Administrator, PowerUser")]
public class ControlPanelController : Controller
{
    public ActionResult SetTime()
    {
    }

    [Authorize(Roles = "Administrator")]
    public ActionResult ShutDown()
    {
    }
}

 

We will look at leveraging the RoleProvider class to make our own custom role provider so that we can have our own application-specific roles without moving users into AD groups. This method can be applied to other forms of authentication other than just Windows.

Custom Role Provider

The Custom role provider is pretty straightforward to create. First, we need to create a new class that inherits from System.Web.Security.RoleProvider. Next, implement the methods you wish to override, and leave the rest throwing a NotImplementedException. In the example below, I override the IsUserInRole and GetRoleForUsers to get user role(s) from the database.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public class CustomRoleProvider : RoleProvider
{
	public CustomRoleProvider(){ }

	public override bool IsUserInRole(string username, string roleName)
	{
		List<string> roles = Users.GetRoles(username); 
		return roles.Count != 0 && roles.Contains(roleName);
	}

	public override string[] GetRolesForUser(string username)
	{
		List<string> roles = Users.GetRoles(username);
		return roles.ToArray();
	}

	#region Not Implemented Methods
	
	public override void AddUsersToRoles(string[] usernames, string[] roleNames)
	{
		throw new NotImplementedException();
	}

	public override string ApplicationName
	{
		get
		{
			throw new NotImplementedException();
		}
		set
		{
			throw new NotImplementedException();
		}
	}

	public override void CreateRole(string roleName)
	{
		throw new NotImplementedException();
	}

	public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
	{
		throw new NotImplementedException();
	}

	public override string[] FindUsersInRole(string roleName, string usernameToMatch)
	{
		throw new NotImplementedException();
	}

	public override string[] GetAllRoles()
	{
		throw new NotImplementedException();
	}

	public override string[] GetUsersInRole(string roleName)
	{
		throw new NotImplementedException();
	}

	public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
	{
		throw new NotImplementedException();
	}

	public override bool RoleExists(string roleName)
	{
		throw new NotImplementedException();
	}
	
	#endregion
}

 

Note: User object is my custom user repository.

Next, we have to configure the application to use our new custom role provider by adding a RoleManager element to the system.web element of the web.config.

1
2
3
4
5
6
<roleManager cacheRolesInCookie="true" defaultProvider="CustomRoleProvider" enabled="true">
  <providers>
	<clear />
	<add name="CustomRoleProvider" type="[Namespace here].CustomRoleProvider, [Application Assembly here]" />
  </providers>
</roleManager>

 

You’re done! Now your application will use the newly created role provider and you can begin to restrict access by your application-specific roles, like we saw in the first example. 

In the next section, I’ll demonstrate how to make a custom authorize attribute that accepts an enum to cut down on magic strings. Note that this is completely optional and is not needed for a custom role provider.

Custom Authorize Attribute

The current authorize attribute is easy to use and works great. But it relies heavily on magic strings. If we passed an enum as the role and if that enum ever changed, the application will no longer compile until the enum is updated throughout the application. In the example below, we make our own authorize attribute that requires an enum.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public enum Roles
{
	User,
	Developer,
	Administrator
}

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class RoleAuthorizeAttribute : AuthorizeAttribute
{
	public RoleAuthorizeAttribute(params object[] roles)
	{
		if (roles.Any(r => r.GetType().BaseType != typeof(Enum)))
			throw new ArgumentException("The roles parameter may only contain enums", "roles");

		var temp = roles.Select(r => Enum.GetName(r.GetType(), r)).ToList(); 
		Roles = string.Join(",", temp);
	}

	public override void OnAuthorization(AuthorizationContext filterContext)
	{
		var request = filterContext.HttpContext.Request; 
		var url = new UrlHelper(filterContext.RequestContext); 
		var accessDeniedUrl = url.Action("AccessDenied", "Error"); 

		if (!string.IsNullOrEmpty(base.Roles))
		{
			var isRoleError = true;
			var rolesAllowed = base.Roles.Split(',');

			var user = filterContext.HttpContext.User;
			if (user != null && rolesAllowed.Any())
			{
				foreach (var role in rolesAllowed)
					if (user.IsInRole(role))
						isRoleError = false;
			}

			if (isRoleError)
			{
				if (request.IsAjaxRequest())
					filterContext.Result = new JsonResult { Data = new { error = true, signinerror = true, message = "Access denied", url = accessDeniedUrl }, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
				else
					filterContext.Result = new RedirectResult(accessDeniedUrl);
			} 
		}
	}
} 

 

First, create a class that inherits from System.Web.Mvc.AuthorizeAttribute. Then, add the constructor to accept the enums and set the base roles. After that, override the OnAuthorization method; this is completely optional unless you wish to change the behavior of what happens when a user does not have access. With Windows authentication, a user is prompted for their credentials when trying to access a page they can’t. I’ve overridden the method to redirect them to a custom access denied page or “return an access denied message if” Ajax request.

1
2
3
4
[RoleAuthorize(Roles.User, Roles.Developer)]
public ActionResult Index()
{
}

 

As you can see, we can now pass an enum and no longer need magic strings.

Conclusion

In this article, we’ve gone over creating a custom role provider and custom authorize attribute to allow for application-specific roles. Hope you’ve enjoyed reading this post and can use my examples as a starting point for your own applications.