Pokud v ASP.NET aplikaci používáme technologii WIF, zpravidla využíváme HTTP modul SessionAuthenticationModule. Ten se stará o to, že po přihlášení je vytvořeno cookie, které obsahuje serializované claims přihlášeného uživatele a při každém requestu provádí rekonstrukci objektu ClaimsPrincipal z dat obsažených v tomto cookie. Navíc pokud naše aplikace podporuje možnost zapamatování přihlášení, je toto cookie vytvořeno jako persistentní, takže při vstupu uživatele do naší aplikace je i při prvním requestu již cookie přítomno a objekt principál obnoven.
Více jsem o autentizaci s využitím technologie WIF, včetně příkladu jednoduché aplikace, psal zde.
Při využívání modulu SessionAuthenticationModule můžeme ale narazit na to, že pokud při obnově claims z cookie dojde k nějaké chybě, je tato chyba standardně odchycena jako neošetřená výjimka. A pokud se právě jedná o obnovování zapamatovaného přihlášení, důsledek je to, že nám aplikace vůbec nenaběhne.
V praxi se s tímto problémem můžeme setkat po změně v konfiguraci běhového prostředí naší aplikace, tedy pokud například změníme identitu, pod kterou běží aplikační pool naší aplikace v IIS nebo pokud dojde ke změně machine key web serveru apod.
Například po změně identity tj. v případě kdy se dekriptování dat cookie provádí pod jinou identitou, než pod tou, která cookie vytvořila, dojde konkrétně k této chybě:
[CryptographicException: Key not valid for use in specified state.]
at System.Security.Cryptography.ProtectedData.Unprotect(Byte[] encryptedData, Byte[] optionalEntropy, DataProtectionScope scope)
at Microsoft.IdentityModel.Web.ProtectedDataCookieTransform.Decode(Byte[] encoded)
[InvalidOperationException: ID1073: A CryptographicException occurred when attempting to decrypt the cookie using the ProtectedData API (see inner exception for details). If you are using IIS 7.5, this could be due to the loadUserProfile setting on the Application Pool being set to false. ]
at Microsoft.IdentityModel.Web.ProtectedDataCookieTransform.Decode(Byte[] encoded)
at Microsoft.IdentityModel.Tokens.SessionSecurityTokenHandler.ApplyTransforms(Byte[] cookie, Boolean outbound)
at Microsoft.IdentityModel.Tokens.SessionSecurityTokenHandler.ReadToken(XmlReader reader, SecurityTokenResolver tokenResolver)
at Microsoft.IdentityModel.Tokens.SessionSecurityTokenHandler.ReadToken(Byte[] token, SecurityTokenResolver tokenResolver)
at Microsoft.IdentityModel.Web.SessionAuthenticationModule.ReadSessionTokenFromCookie(Byte[] sessionCookie)
at Microsoft.IdentityModel.Web.SessionAuthenticationModule.TryReadSessionTokenFromCookie(SessionSecurityToken& sessionToken)
at Microsoft.IdentityModel.Web.SessionAuthenticationModule.OnAuthenticateRequest(Object sender, EventArgs eventArgs)
at Microsoft.Crm.Authentication.Claims.CrmSessionAuthenticationManager.OnAuthenticateRequest(Object sender, EventArgs args)
at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
Pokud problém nastane v debug prostředí, je to sice otravné, ale chybu můžeme případně obejít tím, že si z cache prohlížeče odstraníme příslušné cookie. Pokud ale provedeme nějakou změnu na produkčním hostingu, nebudou z toho mít naši existující uživatele, kteří si nechali zapamatovat přihlášení, vůbec radost (a u nich by jsme znalost s provedením odstraněním cookie obecně předpokládat neměli).
Jak tedy problém řešit?
Řešením je chybu ošetřit tak, že se v takovém případě obnovení přihlášení sice neprovede – uživatelé se budou muset znovu přihlásit (a znovu zvolit zapamatování přihlášení), ale aplikace jim normálně naběhne.
K tomu využijeme událost SessionSecurityTokenReceived, kterou SessionAuthenticationModule nabízí. Její registraci provedeme v souboru global.asax v handleru Application_Start a instanci SessionAuthenticationModulu získáme vlastností SessionAuthenticationModule třídy FederatedAuthentication.
Ve vlastní obsluze události nejprve nastavíme e.Cancel na true, aby se po skončení obsluhy již neprováděla žádná výchozí akce. Pak si provedeme obnovení objektu principál sami (voláním metody AuthenticateSessionSecurityToken) a pro toto volání ošetříme výjimky FederatedAuthenticationSessionEndingException (to modul provádí i standardně) a hlavně SecurityTokenException.
Celý kód ve verzi pro .NET 4.5 vypadá takto:
protected void Application_Start(object sender, EventArgs e)
{
FederatedAuthentication.SessionAuthenticationModule.SessionSecurityTokenReceived += SessionAuthenticationModule_SessionSecurityTokenReceived;
}
protected void SessionAuthenticationModule_SessionSecurityTokenReceived(object sender, SessionSecurityTokenReceivedEventArgs e)
{
e.Cancel = true;
var sessionAuthenticationModule = (SessionAuthenticationModule)sender;
try
{
sessionAuthenticationModule.AuthenticateSessionSecurityToken(e.SessionToken, false);
}
catch (FederatedAuthenticationSessionEndingException)
{
sessionAuthenticationModule.SignOut();
}
catch (System.IdentityModel.Tokens.SecurityTokenException)
{
sessionAuthenticationModule.SignOut();
}
}