asp.net-mvccookiesforms-authentication.aspxauth

How do I invalidate a bad authentication cookie early in the request?


Scenario

I develop an MVC application that will be hosted in Windows Azure. During development, I test against a local database and local user membership service. In production, the application hits off a SQL Azure database and a cloud-hosted user membership service.

Sometimes I'll log in to the local version of the site with a user account that only exists on my local machine. If I forget to log out but switch my machine out of test mode, things start to break.

The Problem

I set my hosts file to point my application URL at 127.255.0.0 so that browser requests are handled by the Azure emulator locally. This also means my test application and the production application use the same host name - cookies for one are seen as valid cookies for the other.

If I log in to my local system, an ASPXAUTH cookie is created for the domain and contains the credentials of the user from my local system. If I forget to log out and switch my hosts file back to normal (browser requests go to the live application), it sends the same ASPXAUTH cookie to the server.

Since this user doesn't exist on the server, only locally, any requests like Membership.GetUser().Email return an object null exception.

What I'd Like to Do

Ideally, I could inject some code early in the request to check that the user exists. Something like:

MembershipUser user = Membership.GetUser();
if(user == null) {
    FormsAuthentication.SignOut();
}

This will automatically remove the ASPXAUTH cookie for invalid users. But where do I put it? I started looking at my controllers - all of them inherit from a BaseController class, but even putting this code in the base controller's constructor isn't early enough. I also looked at Global.asax, but I'm not sure which event would be best to tie this action to.

Also, I'm not even sure this is the right way to do things. If not, what is the best way to ensure that the authentication cookie is for a valid user before just depending on the membership class like I am currently?


Solution

  • You may handle the below event in you global.aspx:

    public void FormsAuthentication_OnAuthenticate(object sender, FormsAuthenticationEventArgs args)
    { 
    }
    

    EDIT:

    In the above method, Membership.GetUser() will return null, since it takes the userName from HttpContext.Current.User.Identity.Name property and in this event the user has not yet been authenticated. The above method is invoked by FormsAuthenticationModule when it raised Authenticate even. You can handle this event like so:

    public void FormsAuthentication_OnAuthenticate(object sender, FormsAuthenticationEventArgs args)
    {
        if (FormsAuthentication.CookiesSupported &&
                Request.Cookies[FormsAuthentication.FormsCookieName] != null)
        {
            try
            {
                var formsAuthTicket = FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value);
    
                MembershipUser user = Membership.GetUser(formsAuthTicket.Name);
                if (user == null)
                {
                    FormsAuthentication.SignOut();
                    Request.Cookies[FormsAuthentication.FormsCookieName].Value = null;
                }
            }
            catch (Exception ex)
            {
                //TODO:log ex
            }
        }
    }
    

    You can also consider to handle the AuthenticateRequest event by declaring the below method in the global.aspx.

    NOTE: Application_AuthenticateRequest event is fired after the FormsAuthentication_OnAuthenticate event.

    void Application_AuthenticateRequest(object sender, EventArgs e)
    {
        if (HttpContext.Current.User != null && HttpContext.Current.User.Identity.IsAuthenticated)
        {                
            MembershipUser user = Membership.GetUser();
            if (user == null)
            {
                FormsAuthentication.SignOut();
                HttpContext.Current.User = null;
            }
        }            
    }
    

    Hope this helps..