ASP.NET Identity Ouath 登入 null 問題(loginInfo is null)

  • 646
  • 0
  • 2015-12-09

ASP.NET Identity Ouath(facebook) 登入 null 問題 (loginInfo is null)

最近有幾個案子要辦 facebook 行銷活動,因為都是小案子,所以就直接用了 Visual Studio 2015 內建的範本,其中就包含了 ASP.NET Identity

在開發與測試時都非常順利,然後就上線了

於上線運作階段偶爾會接到使用者反映使用 facebook 登入時,點選登入後經過 facebook 驗證成功,可是又回到了登入頁面

於初期完全無法根據特定條件重現問題,偶爾會發現但是下一次卻又成功

原因

根據多方 GOOGLE 後,使用關鍵字:asp.net identity, login, owin, null
在 StackOverflow 查到了 ASP.NET MVC 5 (VS2013 final): Facebook login with OWIN fails (loginInfo is null)

在此篇文章中有一個連結 ASP.NET_SessionId + OWIN Cookies do not send to browser
(此篇講解為什麼會出問題,並附上落落長的解答,但等等會提供佛心大大提供的直接解決方案)

一些背景知識

以下說明根據的 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 使用自己的抽象化方式來實作 Cookie (Microsoft.Owin.ResponseCookieCollection),它實作的方式是直接將 Response Header 集合包起來並從而更新 Set-Cookie Header
OWIN ASP.NET Host (Microsoft.Owin.Host.SystemWeb) 直接將 System.Web.HttpResponse 與 Header 集合整個包起來
因此當 OWIN 建立新 Cookie 時,Response 的 Set-Cookie Header 直接被 OWIN 改成 OWIN 自己的格式

但 ASP.NET 針對 Response Cookies 也有自己的抽象化方式,可以透過 sealed class System.Web.HttpCookieCollection 中之 System.Web.HttpResponse.Cookies 屬性與實作略為窺知
它的實作方法並不會直接改變 Set-Cookie Header,而是使用了少數最佳化過的 Internal Notifications 來將狀態的變更寫入 Response 物件

在 Requert 的生命週期的特定時間點中,當 HttpCookieCollection 狀態變更完畢 (System.Web.HttpResponse.GenerateResponseHeadersForCookies()) 且 Cookies 已經序列化到 Set-Cookie Header 中
假如這個集合正好處於某些特殊狀態下時,整個 Set-Cookie Header 會先被整個清空後,才根據集合中的 Cookies 重新建立

ASP.NET Session 的實作是使用 System.Web.HttpResponse.Cookies 屬性來儲存 ASP.NET_SessionId Cookie,且在 ASP.NET Session 的模組 (System.Web.SessionState.SessionStateModule) 中有做了一些最佳化,透過一個靜態屬性名為:s_sessionEverSet,根據這個名字就可以大概一窺它的功用;假設你有將任何資料存入至 Session State 中,那麼這個模組就會自動幫你在每個 Request 中多做一點額外的處理


回到問題

透過以上的一些知識就可以來解釋這些靈異現象

Case 1. 從沒使用過 Session

System.Web.SessionState.SessionStateModule, s_sessionEverSet 屬性為 false
沒有任何 session id 被 session state 模組產生且 System.Web.HttpResponse.Cookies 集合狀態沒有偵測到任何變更
在這情況下,OWIN Cookies 被正確送出給瀏覽器並成功登入

Case 2. Session 在應用程式某處被使用過, 但使用者尚未嘗試登入過

System.Web.SessionState.SessionStateModule 與 s_sessionEverSet 屬性為 true
Session Id 由 SessionStateModule 產生,並將 ASP.NET_SessionId 加入至 System.Web.HttpResponse.Cookies 集合中,但在稍後將因 Session 是空的所以被清除掉
在這狀況下 System.Web.HttpResponse.Cookies 集合狀態被偵測到改變,因此 Set-Cookie Header 先被清除後再次將 Cookies 重新序列化到 Header
因此這樣可以得知 OWIN Response Cookies 其實已經不見了,使用者被判斷為沒有登入並重新導向到登入頁面

Case 3 - 再登入之前已經使用過 Session

System.Web.SessionState.SessionStateModule 與 s_sessionEverSet 屬性為 true
Session Id 由 SessionStateModule 產生,並將 ASP.NET_SessionId 加入至 System.Web.HttpResponse.Cookies 集合中
因為 System.Web.HttpCookieCollection 內部的最佳化機制,System.Web.HttpResponse.GenerateResponseHeadersForCookies() Set-Cookie Header 並沒有立刻被清除,而只有被更新

在這狀況下 OWIN authentication cookies 與 ASP.NET_SessionId cookie 都被正確送出,因此登入成功

更普通的說

從上面的說明可以看出這問題不只發生在 ASP.NET session 上,只要你是透過 Microsoft.Owin.Host.SystemWeb 來 Hosting OWIN ,你的 System.Web.HttpResponse.Cookies 都會有機會發生問題

舉例來說
以下為正常運作範例

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

    return View();
}

 

但以下範例就不會正常運作且 OwinCookie 也會遺失

不會正常運作
public ActionResult Index()
{
    HttpContext.GetOwinContext()
        .Response.Cookies.Append("OwinCookie", "SomeValue");
    HttpContext.Response.Cookies["ASPCookie"].Value = "SomeValue";
    HttpContext.Response.Cookies.Remove("ASPCookie");

    return View();
}

以上範例使用 VS2013, IISExpress and 預設 MVC project 範本進行驗證


在這邊有個大神直接寫了 nuget package 出來:http://stackoverflow.com/a/26978166

Package Manager Console
Install-Package Kentor.OwinCookieSaver 

 

安裝 nuget 套件後,直接在 ConfigureAuth 方法上設定

App_Start/Startup.Auth.cs
app.UseKentorOwinCookieSaver(); 
app.UseCookieAuthentication(new CookieAuthenticationOptions());

 

如此乾淨俐落的解決,問題再也沒出現過

原文來自:http://blog.developer.money/posts/301383-aspnet-identity-null-reference-logininfo-is-null

學習學到老,撞牆撞到腦