ASP.NET_SessionId + OWIN Cookies do not send to browser

后端 未结 9 1244
误落风尘
误落风尘 2020-11-22 14:00

I have a strange problem with using Owin cookie authentication.

When I start my IIS server authentication works perfectly fine on IE/Firefox and Chrome.

I st

相关标签:
9条回答
  • 2020-11-22 14:35

    I had the same symptom of the Set-Cookie header not being sent but none of these answers helped me. Everything worked on my local machine but when deployed to production the set-cookie headers would never get set.

    It turns out it was a combination of using a custom CookieAuthenticationMiddleware with WebApi along with WebApi compression support

    Luckily I was using ELMAH in my project which let me to this exception being logged:

    System.Web.HttpException Server cannot append header after HTTP headers have been sent.

    Which led me to this GitHub Issue

    Basically, if you have an odd setup like mine you will want to disable compression for your WebApi controllers/methods that set cookies, or try the OwinServerCompressionHandler.

    0 讨论(0)
  • 2020-11-22 14:36

    If you are setting cookies in OWIN middleware yourself, then using OnSendingHeaders seems to get round the problem.

    For example, using the code below owinResponseCookie2 will be set, even though owinResponseCookie1 is not:

    private void SetCookies()
    {
        var owinContext = HttpContext.GetOwinContext();
        var owinResponse = owinContext.Response;
    
        owinResponse.Cookies.Append("owinResponseCookie1", "value1");
    
        owinResponse.OnSendingHeaders(state =>
        {
            owinResponse.Cookies.Append("owinResponseCookie2", "value2");
        },
        null);
    
        var httpResponse = HttpContext.Response;
        httpResponse.Cookies.Remove("httpResponseCookie1");
    }
    
    0 讨论(0)
  • 2020-11-22 14:37

    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.

    0 讨论(0)
提交回复
热议问题