anteater
anteater

Reputation: 55

Accessing custom principal within a custom ActionFilterAttribute

I am working on an ASP.NET MVC application. I have implemented custom membership provider, principal and identity. In the custom provider I replace the HttpContext.Current.User in the ValidateUser() method as follows:

public sealed class CustomMembershipProvider : MembershipProvider {  
    ...  
    public override bool ValidateUser(string username, string password) {  
      ...  
      CustomIdentity identity = new CustomIdentity(...);  
      CustomPrincipal cu = new CustomPrincipal(identity);  
      HttpContext.Current.User = cu;  
      ...  
    }  
    ...  
}

In the AccountController (calls the custom membership provider) I am able to access the custom identity as follows:

public class AccountController : BaseController {  
  ...  
  public ActionResult LogOn(string userName,   
                            string password,   
                            bool rememberMe,   
                            string returnUrl) {  
    ...  
    CustomIdentity ci = (CustomIdentity)HttpContext.User.Identity;  
    ...  
  }  
  ...  
}

All my controllers inherit the BaseController which calls a custom attribute as follows:

[CustomAttribute]  
public abstract class BaseController : Controller {  
  ...  
}  

I want my other controllers to access the custom identity within the custom attribute after it has been set by the AccountController as follows:

public class CustomAttribute : ActionFilterAttribute {  
  public override void OnActionExecuting(ActionExecutingContext filterContext) {  
    base.OnActionExecuting(filterContext);  
    CustomIdentity ci = filterContext.HttpContext.User.Identity as CustomIdentity;  
    ...  
    }  
  }  
}  

What I have found is that filterContext.HttpContext.User is still set to GenericPrincipal and not my CustomPrincipal. So my custom identity is not accessible within my attribute filter. What do I have to do so that my CustomPrincipal is accessible within my attribute filter?

Thanks in advance.

Upvotes: 2

Views: 4133

Answers (2)

Necros
Necros

Reputation: 3034

I don't know if this is "better" way, but it worked for me so far. I create a static UserContext class that has CurrentUser property. There I store the user entity I get from database and use it for user info data and authorization. I only use the HttpContext.Current.User to check authentication.

Now the CurrentUser property stores my user object in HttpContext's Items colletion (I have a wrapper around that so I can make it unit testable).

Upvotes: 0

anteater
anteater

Reputation: 55

After researching more about how application request events are fired (the specific order) and when context objects can be set I was able to set my custom principal and identity so that they are available to the filter (throughout the application for that matter).

I realized that the user must be authenticated before these entities could be set for use through the rest of the application. This, I found, could be done in the Application_AuthenticateRequest() method of global.asax.

So, I modified my logic as follows:

  1. Removed creating custom principal and identity from the custom provider's ValidateUser() method.
  2. Instead, ValidateUser() after verifying username and password against the custom repository, caches whatever information I needed within HttpContext.Current.Cache using the user name as the unique key.
  3. Finally, I added the following logic in Application_AuthenticateRequest() to set set my custom principal and identity by extracting the generic identity properties and extending it with custom properties that I stored in the cache. I indexed into the cache using the name stored within generic identity as that is the key I used to create the cache.
protected void Application_AuthenticateRequest(object sender, EventArgs e) {  

  if (Request.IsAuthenticated) {  

    // TODO: Add checks so we only do the following once per login.

    // Get the GenericPrincipal identity  
    IIdentity ui = HttpContext.Current.User.Identity;  

    /* Extract Name, isAuthenticated, AuthenticationType from
       the identity of the GenericPrincipal and add them including 
       any custom properties to the custom identity. I added a 
       few extra properties to my custom identity. */

    CustomIdentity identity = new CustomIdentity(...);

    /* Although my custom principal does not currently 
       have any additional properties, I created a new  
       principal as I plan to add them in the future. */

    CustomPrincipal principal = new CustomPrincipal(identity);

    // Set custom principal 
    HttpContext.Current.User = principal;

  }
}

This got me past my hurdle. Please guide me if there are other better ways to accomplish the same.

Thanks.

Upvotes: 2

Related Questions