ASP.NET Identity Ouath 登入 null 問題

aspnet-identity-null-reference-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 都被正確送出,因此登入成功
看到這邊你就會突然知道為什麼之前的搜尋結果都說先用 Session 再登入就會成功,這真的是知其然而不知所以然阿!
更普通的說
從上面的說明可以看出這問題不只發生在 ASP.NET session 上,只要你是透過 Microsoft.Owin.Host.SystemWeb 來 Hosting OWIN ,你的 System.Web.HttpResponse.Cookies 都會有機會發生問題
舉例來說 以下為正常運作範例
正常運作
但以下範例就不會正常運作且 OwinCookie 也會遺失
不會正常運作
以上範例使用 VS2013, IISExpress and 預設 MVC project 範本進行驗證
解決方法
在這邊有個大神直接寫了nuget package出來:http://stackoverflow.com/a/26978166
nuget package
安裝nuget套件後,直接在ConfigureAuth方法上設定
app.UseKentorOwinCookieSaver();
如此乾淨俐落的解決,問題再也沒出現過

![[IT 鐵人賽] ASP.NET Core 與 Log 紀錄和追蹤的愛恨交織 - Day 05 - Elmah - 02](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1776070454149%2Fa0702417-5357-4217-a572-7ba53ea36735.png&w=3840&q=75)
![[IT 鐵人賽] Azure Data Service - Day 04 - Cognitive Service - 辨識 - Video Indexer](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1776070473207%2F6423fd4a-4041-4bb2-b918-4cfe3f9aea5e.png&w=3840&q=75)