Custom Role Provider has issue with AuthorizeAttribute for MVC

1k views Asked by At

I am developing a MVC 5 application with custom role provider, but it seems that the AuthorizeAttribute never call my customer role provider, my code is as below:

My Customer provider:

namespace MyDomain
{
    public class CustomRoleProvider : RoleProvider
    {
         public override string[] GetRolesForUser(string username)
        {
            using (MyContext objContext = new MyContext())
            {
                var objUser = objContext.Users.FirstOrDefault(x => x.Username == username);
                if (objUser == null)
                {
                    return null;
                }
                else
                {
                    string[] ret = { objUser.Access_Levels.Name };
                    return ret;
                }
            }
        }

        public override bool IsUserInRole(string username, string roleName)
        {
            var userRoles = GetRolesForUser(username);
            return userRoles.Contains(roleName);
        }
}

My controller:

[Authorize(Roles = "Administrator")]
public class AdminController : Controller

And Web.Config:

 <system.web>
    <roleManager defaultProvider="CustomRoleProvider" enabled="true" >
      <providers>
        <clear />
        <add name="CustomRoleProvider" type="Online_Storage_Portal.CustomRoleProvider"  cacheTimeoutInMinutes="30"/>
      </providers>
    </roleManager>
  </system.web>

Also my custom role provider is in the same project as my other controllers, I am able to call my custom role provider method with following code within my controller

String[] roles = Roles.GetRolesForUser(username)

but the controller with [Authorize(Roles = "Administrator")] always redirect the page to login screen even the user login and role are both valued.

Please help!!

1

There are 1 answers

2
Dan Caswell On

I believe I've found the source of your problem. I'm going to assume you're using Windows Authentication, and trying to use your custom role provider in place of the Windows Groups that are automatically loaded. Looking into the MVC AuthorizeAttribute source, you'll find that it is actually calling Principal.IsInRole. Per MSDN:

InRole first checks the IsRoleListCached property to determine whether a cached list of role names for the current user is available. If the IsRoleListCached property is true, the cached list is checked for the specified role. If the IsInRole method finds the specified role in the cached list, it returns true. If IsInRole does not find the specified role, it calls the GetRolesForUser method of the default Provider instance to determine whether the user name is associated with a role from the data source for the configured ApplicationName value.

So I'm guessing that because the Principal is a Windows Principal, it is coming in with it's roles populated and cached. When the IsInRole is called, it says 'Hey, I've already got roles, why would I go back to the provider to get them again?"

What you could do instead would be somthing like this:

protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
    {
        WindowsIdentity identity = HttpContext.Current.Request.LogonUserIdentity;
        HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(new GenericIdentity(identity.Name), Roles.GetRolesForUser());
    }

This will pull the windows identity off the HttpContext, use the name to explicitly fetch roles from your custom provider, and then slap a new GenericPrincipal on the request instead. I went further and implemented some logic to store the roles in an encrypted cookie so we don't have to go the role provider on each request.

 void Application_PostAuthenticateRequest()
    {
        HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
        FormsAuthenticationTicket authTicket;
        if (authCookie == null || authCookie.Value == "")
        {
            string[] getRoles = Roles.GetRolesForUser();
            authTicket = new FormsAuthenticationTicket(1,
                User.Identity.Name,
                DateTime.Now,
                DateTime.Now.AddMinutes(20),
                true,
                String.Join(";", getRoles));

            string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
            authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
            HttpContext.Current.Response.Cookies.Add(authCookie);
        }
        try
        {
            authTicket = FormsAuthentication.Decrypt(authCookie.Value);
        }
        catch
        {
            return;
        }
        string[] roles = authTicket.UserData.Split(';');
        if (Context.User != null)
            Context.User = new System.Security.Principal.GenericPrincipal(Context.User.Identity, roles);
    }