CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Kyle Baley - The Coding Hillbilly

"We are stuck with technology when what we really want is just stuff that works" -- Douglas Adams

"Server cannot modify cookies" error in ASP.NET MVC

This post falls into the "Google-able errors" category and won't be of much use to anyone who isn't get the specific error.

Here's the error I was getting: Server cannot modify cookies after http headers have been sent

This is in my ASP.NET MVC application and the offending code was the last line of the following method:

public void SignIn( User user )
{
    FormsAuthentication.SignOut( );

    var issued = DateTime.Now;
    var expires = issued.AddDays( 1 );
    var roles = "Administrator";

    var ticket = new FormsAuthenticationTicket( 1, user.UserName, issued, expires, false, roles );
    var encryptedTicket = FormsAuthentication.Encrypt( ticket );
    var authCookie = new HttpCookie( FormsAuthentication.FormsCookieName, encryptedTicket )
        {
            Expires = ticket.Expiration
        };

    _httpContext.Response.Cookies.Add( authCookie );
}

This is in a class called Authentication Service which is called from my LoginController. The authentication service is wired up to the LoginController using Dependency Injection via Windsor by way of MvcContrib. Consider that foreshadowing.

The key to the error is the _httpContext variable. It is set in the constructor of the AuthenticationService as follows:

public AuthenticationService( ) : this( new HttpContextWrapper2( HttpContext.Current ) )
{
}

public AuthenticationService( HttpContextBase httpContext )
{
    _httpContext = httpContext;
}

Some of you will recognize this as poor man's dependency injection, used primarily because I didn't feel like wrapping the HttpContextWrapper2 class (which is itself a wrapper) in an interface. Some of you will also note that even though I'm injecting the HttpContext into this class, it is still largely untestable because of the FormsAuthentication stuff. Frankly, I'm good with that which is why I didn't bother with the interface.

During my debugging, one of the things I did was replace _httpContext with HttpContext.Current and lo! That works fine. So clearly, my poor man's dependency injection had issues. My mentor application, CodeCampServer, went the extra step and created an IHttpContextProvider interface around HttpContextWrapper2 and used Windsor to wire it in:

public AuthenticationService(IClock clock, IHttpContextProvider httpContextProvider)
{
    _clock = clock;
    _httpContextProvider = httpContextProvider;	        
}

IHttpContextProvider has a pretty simply interface and implementation:

public class HttpContextProvider : IHttpContextProvider
{
    public HttpContextBase GetCurrentHttpContext()
    {
        return new HttpContextWrapper2(HttpContext.Current);
    }
}

So while I originally poo-poohed the extra layer of abstraction, CodeCampServer had a key feature that my application did not in that it's login facility worked.

So I introduced an IHttpContextProvider interface and replaced my existing _httpContext with it and lo! I am able to log in successfully. Here is the updated AuthenticationService:

public class AuthenticationService : IAuthenticationService
{
    private readonly IHttpContextProvider _httpContext;

    public AuthenticationService( IHttpContextProvider httpContext )
    {
        _httpContext = httpContext;
    }

    public void SignIn( User user )
    {
        FormsAuthentication.SignOut( );

        var issued = DateTime.Now;
        var expires = issued.AddDays( 1 );
        var roles = "Administrator";

        var ticket = new FormsAuthenticationTicket( 1, user.UserName, issued, expires, false, roles );
        var encryptedTicket = FormsAuthentication.Encrypt( ticket );
        var authCookie = new HttpCookie( FormsAuthentication.FormsCookieName, encryptedTicket )
             {
                 Expires = ticket.Expiration
             };

        _httpContext.GetCurrentHttpContext( ).Response.Cookies.Add( authCookie );
    }
}

One of the nice things that came out of this was that, thanks to my auto-registration code, I didn't need to do anything special to wire in the new IHttpContextProvider class. Just created it and it just worked. Who knew?

As for the cause of the error, I can only speculate. My guess is that it was using the wrong HttpContext. During my debugging, the constructor for AuthenticationService would get called during the first call to the Login page. That is, when the Login page was first rendered. So the class was storing a reference to *that* page's HTTP context. When the user logged in, it would use that context and try to add the authentication cookie to it. Since that context had already been used and the headers written out, the error occurred. So it wasn't actually getting the *current* HTTP context. It was using the previous request's context.

Further proof of this theory: when I would see that error, my workaround was to go back to Visual Studio and do a full re-compile of the application. This clears out the cached version of the class, and more importantly, it's reference to the previous request's HTTP context. When I'd refresh the page, it would have to create a new instance of AuthenticationService and use the current request's HTTP context, which hasn't yet completed.

In the new version, every call to GetCurrentHttpContext will create a new HttpContextWrapper2 object based on the current request's HTTP context. Ergo, we will always have the current context.

Whatever, as long as it works.

Kyle the Theoretical


Published May 24 2008, 12:41 PM by Kyle Baley
Filed under:

Comments

Kyle Baley said:

@Joe: Although the app is meant to be used internally, the company is small enough that they don't have the infrastructure in place to host it internally. Hence, it will be hosted externally.

# May 24, 2008 2:27 PM

Joe said:

Ok...but what's that got to do with FormsAuthentication? I'm just curious as in why you're using it if the whole concept of forms doesn't even exist in MVC.

# May 24, 2008 4:49 PM

Kyle Baley said:

MVC obviates the need for a lot of things but authentication is not one of them. I still have the need to store a user's identity across a session. And FormsAuthentication is still useful in this regard.

Note that FormsAuthentication doesn't make use of ViewState, the chief target of MVC-proponents. It's an abstraction over cookies. Without it, I'd have to manage the cookies used for authentication myself.

I sound a lot more confident than I really am in this regard. In reality, I stole the code from CodeCampServer

# May 24, 2008 5:36 PM

Kyle Baley said:

Oh, and the concept of forms *does* exist in MVC. It's WebForms, the abstraction over HTTP forms, that MVC tosses out.

# May 24, 2008 5:37 PM

Andy Hitchman said:

Your original implementation of the AuthenticationService was stateful.

I'd guess that you need to mark the component as transient so Windsor constructs a new instance per resolution. By default (IIRC), it caches component instances which effectively act as singletons.

# May 24, 2008 6:29 PM

Kyle Baley said:

@Andy: My original implementation didn't use Windsor at all. It used poor man's dependency injection. But yes, it was stateful. Really, I could have fixed this by not creating an HttpContextWrapper2 object in the constructor but in the SignIn method itself. When I switched to using Windsor, it worked because my auto-registration code adds objects as transient.

# May 24, 2008 7:11 PM

Joe said:

I just don't know why there's a need for FormsAuthentication. You can set the cookie yourself and validate it's existence before each action call (via some inherited controller).

# May 25, 2008 1:25 AM

Kyle Baley said:

As I mentioned, managing the cookie yourself is a possibility. But that's what FormsAuthentication is there for, to do it for you. To check if someone is authenticated, you need only call: HttpContext.Current.User.Identity.IsAuthenticated.

FormsAuthentication is an entirely separate concept from WebForms and MVC. It doesn't use the ASP.NET Lifecycle, which is the typical target for people moving away from WebForms. You could just as easily manage the cookies yourself in WebForms.

# May 25, 2008 10:11 AM

JH said:

Kyle,

  Not certain if you've already come across it already, but the MVC Membership Starter Kit has give us a good headstart on authentication stuff on our project.

www.codeplex.com/MvcMembership

# May 26, 2008 12:15 PM

Kyle Baley said:

@Jason: I had not seen that. Looks interesting though perhaps a little bit of overkill for my requirements, especially since I've already got the authentication working. Will take a look when my requirements get more complex though.

# May 26, 2008 12:48 PM

The ASP.NET MVC Information Portal said:

Pingback from  The ASP.NET MVC Information Portal

# June 17, 2008 2:27 AM

Leave a Comment

(required)  
(optional)
(required)  

Enter the numbers above:
Add
Check out Devlicio.us!

Our Sponsors

Proudly Partnered With