Multiple providers with ASP.NET Forms Authentication

Microsoft claims that it is easy to setup multiple member and role providers in .NET. While that appears to be relatively true for configuration, getting Forms Authentication (most particularly, the ASP.NET Login control) to respect the multiple providers is much more difficult. I spent probably 3 hours this weekend trying to find good solutions to this problem… Many were present, but no single one solution provided the behavior I was looking for.

Ultimately the problem was related to the fact that the Login control is only intended for one membership provider (the default configured provider). One of the first posts I found indicated that you have to hook into the “Authenticate” event of the Login control so that you can validate against multiple membership providers, rather than the default configured membership provider (which is the default behavior of the Login control). I did this… However, the problem I ended up with was that the RolePrincipal.ProviderName property set in HttpContext.Current.User was not the provider that I had authenticated with. This caused the ASP.NET authentication mechanism to use the default configured role manager instead of the role manager intended for the membership provider that the user was authenticated with.

At first I thought I would programmatically change the MembershipProvider property of the Login control so that it would not use the default configuration membership provider, but for some reason that never worked.

Next I decided to try use the custom principal and store the correct membership provider along with the principal. Attempting that, however, caused a “Type not found for member” exception, supposedly because the principal is serialized to the app domain, rather than to the user context. There were suggestions to implement my own version of ISerializable on the principal and identity objects (which I tried), but that didn’t work either. There was also a suggestion to load the assembly that contained the IPrincipal and IIdentity objects into the GAC. I tried that as well, with no good results. I ended up ditching the custom principal and identities concept…

Lastly, I found a post saying I should store the extra information in the UserData property of the FormsAuthenticationTicket, and then set the data to a new RolePrincipal object within the Global.asax’s Application_AuthenticateRequest() method. That worked the first time, but subsequent loads to a page would lose information. Eventually I discovered the Application_PostAuthenticateRequest() method, which worked.

So, here is the fruits of my labor on the subject, in several small examples:

Login.aspx

protected void LoginUser_Authenticate(object sender, AuthenticateEventArgs e)
{
    this.authenticated =
        e.Authenticated = AuthHelper.Login(this.LoginUser.UserName, this.LoginUser.Password, out this.authenticatedProvider);
}

protected void LoginUser_LoggedIn(object sender, EventArgs e)
{
    if (this.authenticated)
    {
        string userData = string.Format("Provider={0}", authenticatedProvider);
        FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1, this.LoginUser.UserName, DateTime.Now, DateTime.Now.AddHours(2), true, userData);
        string encAuthTicket = FormsAuthentication.Encrypt(authTicket);
        HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encAuthTicket);

        if (Response.Cookies[FormsAuthentication.FormsCookieName] != null)
            Response.Cookies.Set(faCookie);
        else
            Response.Cookies.Add(faCookie);
    }
}

Global.asax

protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
    HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];

    if (authCookie != null)
    {
        FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);

        if (!string.IsNullOrEmpty(authTicket.UserData))
        {
            string[] parts = authTicket.UserData.Split('=');

            if (parts.Length == 2 && parts[0] == "Provider")
            {
                RolePrincipal newRolePrincipal = new RolePrincipal(parts[1], HttpContext.Current.User.Identity);
                HttpContext.Current.User = newRolePrincipal;
                Thread.CurrentPrincipal = newRolePrincipal;
            }
        }
    }
}

In the end, using the above code, I was able to make calls to HttpContext.Current.User.IsInRole() for any user authenticated on any membership provider and it would get directed to the appropriate role provider based on the ProviderName set in the principal from the PostAuthenticateRequest() method.

Leave a Reply

Your email address will not be published.

Humanity Verification *Captcha loading...