ASP.NET_SessionId + OWIN Cookies do not send to browser

I have encountered the same problem and traced the cause to OWIN ASP.NET hosting implementation. I would say it’s a bug.

Some background

My findings are based on these assembly versions:

  • Microsoft.Owin, Version=2.0.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
  • Microsoft.Owin.Host.SystemWeb, Version=2.0.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
  • System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a

OWIN uses it’s own abstraction to work with response Cookies (Microsoft.Owin.ResponseCookieCollection). This implementation directly wraps response headers collection and accordingly updates Set-Cookie header. OWIN ASP.NET host (Microsoft.Owin.Host.SystemWeb) just wraps System.Web.HttpResponse and it’s headers collection. So when new cookie is created through OWIN, response Set-Cookie header is changed directly.

But ASP.NET also uses it’s own abstraction to work with response Cookies. This is exposed to us as System.Web.HttpResponse.Cookies property and implemented by sealed class System.Web.HttpCookieCollection. This implementation does not wrap response Set-Cookie header directly but uses some optimizations and handful of internal notifications to manifest it’s changed state to response object.

Then there is a point late in request lifetime where HttpCookieCollection changed state is tested (System.Web.HttpResponse.GenerateResponseHeadersForCookies()) and cookies are serialized to Set-Cookie header. If this collection is in some specific state, whole Set-Cookie header is first cleared and recreated from cookies stored in collection.

ASP.NET session implementation uses System.Web.HttpResponse.Cookies property to store it’s ASP.NET_SessionId cookie. Also there is some basic optimization in ASP.NET session state module (System.Web.SessionState.SessionStateModule) implemented through static property named s_sessionEverSet which is quite self explanatory. If you ever store something to session state in your application, this module will do a little more work for each request.


Back to our login problem

With all these pieces your scenarios can be explained.

Case 1 – Session was never set

System.Web.SessionState.SessionStateModule, s_sessionEverSet property is false. No session id’s are generated by session state module and System.Web.HttpResponse.Cookies collection state is not detected as changed. In this case OWIN cookies are sent correctly to the browser and login works.

Case 2 – Session was used somewhere in application, but not before user tries to authenticate

System.Web.SessionState.SessionStateModule, s_sessionEverSet property is true. Session Id’s are generated by SessionStateModule, ASP.NET_SessionId is added to System.Web.HttpResponse.Cookies collection but it’s removed later in request lifetime as user’s session is in fact empty. In this case System.Web.HttpResponse.Cookies collection state is detected as changed and Set-Cookie header is first cleared before cookies are serialized to header value.

In this case OWIN response cookies are “lost” and user is not authenticated and is redirected back to login page.

Case 3 – Session is used before user tries to authenticate

System.Web.SessionState.SessionStateModule, s_sessionEverSet property is true. Session Id’s are generated by SessionStateModule, ASP.NET_SessionId is added to System.Web.HttpResponse.Cookies. Due to internal optimization in System.Web.HttpCookieCollection and System.Web.HttpResponse.GenerateResponseHeadersForCookies() Set-Cookie header is NOT first cleared but only updated.

In this case both OWIN authentication cookies and ASP.NET_SessionId cookie are sent in response and login works.


More general problem with cookies

As you can see the problem is more general and not limited to ASP.NET session. If you are hosting OWIN through Microsoft.Owin.Host.SystemWeb and you/something is directly using System.Web.HttpResponse.Cookies collection you are at risk.

For example this works and both cookies are correctly sent to browser…

public ActionResult Index()
{
    HttpContext.GetOwinContext()
        .Response.Cookies.Append("OwinCookie", "SomeValue");
    HttpContext.Response.Cookies["ASPCookie"].Value = "SomeValue";

    return View();
}

But this does not and OwinCookie is “lost”…

public ActionResult Index()
{
    HttpContext.GetOwinContext()
        .Response.Cookies.Append("OwinCookie", "SomeValue");
    HttpContext.Response.Cookies["ASPCookie"].Value = "SomeValue";
    HttpContext.Response.Cookies.Remove("ASPCookie");

    return View();
}

Both tested from VS2013, IISExpress and default MVC project template.

Leave a Comment