Asp.net Forms Authentication - Principal changes?

Go To StackoverFlow.com

1

I'm having an odd problem with Forms authentication. I have my own custom principal and identity classes, and after sign-in I set HttpContext.Current.User to this principal, as well as storing it in the cache (HttpContext.Current.Cache). It seems that after some time passes, the odd behavior begins. This is my AuthenticateRequest handler:

protected void Application_AuthenticateRequest( object sender, EventArgs e ) {
    string userName;

    var formAuthCookie = HttpContext.Current.Request.Cookies[ FormsAuthentication.FormsCookieName ];
    var isAuthenticated = HttpContext.Current.Request.IsAuthenticated;

    if ( isAuthenticated || formAuthCookie != null ) {
        if ( !isAuthenticated ) {
            var ticket = FormsAuthentication.Decrypt( formAuthCookie.Value );
            userName = ticket.Name;
        }
        else {
            userName = HttpContext.Current.User.Identity.Name;
        }

        var prin = (IPrincipal)HttpContext.Current.Cache[ userName ];

        if ( prin != null ) {
            HttpContext.Current.User = prin;
        }
    }
}

This always works fine; the custom principal is pulled from the cache and correctly set into the Current context's user.

The problem is that when I get to the page load, the Page.User property has a GenericPrincipal (with no roles) and a FormsIdentity. I have no idea where this is happening. Of course the page then doesn't work as the user is not in the proper role, although FormsAuth let them into a role restricted page.

Any ideas why the princpal I set in the AuthenticateRequest handler is being replaced?

2012-04-05 01:25
by Andy
You do realize that FormsAuthentication is cookie based, while cache is AppDomain based, meaning everytime the app domain recyles, everything it is lost.. - Erik Funkenbusch 2012-04-05 01:59
The app pool is not be recycled. Actually you reminded me, recycling the app pool seems to correct the problem for a bit. The cookie is set to expire at the same time as the session as well. The problem is not that the cache is being lost, as I can trace the code above and see the correct principal get pulled out and literally seconds later in the page_load I have a different principal - Andy 2012-04-05 02:04
You should never set the session to expire at the same time as the forms auth cookie. http://completedevelopment.blogspot.com/2009/12/caution-with-using-sessiontimeout-and.htm - Erik Funkenbusch 2012-04-05 02:10
@MystereMan Good information, but this problem happens immediately after sign-in. Its also been working correctly since .Net 2.0, until recently - Andy 2012-04-05 12:53
I always use Context.User rather than HttpContext.Current.User, not sure if there is a difference - Erik Funkenbusch 2012-04-05 14:42


-1

Try this instead:

var formAuthCookie = Context.Request.Cookies[ FormsAuthentication.FormsCookieName ]; 
var isAuthenticated = Context.Request.IsAuthenticated; 

if ( isAuthenticated || formAuthCookie != null ) { 
    if ( !isAuthenticated ) { 
        var ticket = FormsAuthentication.Decrypt( formAuthCookie.Value ); 
        userName = ticket.Name; 
    } 
    else { 
        userName = Context.User.Identity.Name; 
    } 

    var prin = (IPrincipal)Context.Cache[ userName ]; 

    if ( prin != null ) { 
        Context.User = prin; 
    } 
} 
2012-04-05 14:44
by Erik Funkenbusch
Can you highlight tthe differences between this and what I have - Andy 2012-04-05 18:43
@Andy - It's a small code block, the changes are obvious. you just change HttpContext.Current.* to Context. - Erik Funkenbusch 2012-04-05 19:27
This is no difference - bang 2012-07-11 01:02
@bang - there is a difference. Trust me, I pulled my hair out over this one myself. The difference is that Context.* is a wrapper around the HttpContext, and this wrapper gets reset (clearing your changes) if you set it at the wrong place - Erik Funkenbusch 2012-07-11 04:35
@MystereMan Sorry. Thought it was the Page.Context and not global.asax you refered to. Sounds very strange but you might be right here. Never uses global.asax. Could you please explain why they are difference - bang 2012-07-11 09:27
With Global.asax, you do the code once and it applies to everything. It doesn't require that you copy the code, or use a common base class. As to why it makes a difference here is that MVC wraps the context in a shim class to make testing easier. If you change HttpContext after this shim has been created, then the old values will still exist in the wrapper class, even though they exist in HttpContext. You don't want to mix the two. Either use only wrapper, or only use HttpContext - Erik Funkenbusch 2012-07-11 15:50
@MystereMan You're right about the wrapper, however the wrapper is just a pass through to the instance given to the wrapper's ctor; the reference should be the same. The only way your solution makes sense is if I end up with a different instance of HttpContext for the same request. To my knowledge, that should never happen - Andy 2012-08-13 11:58
Ads