I implemented an ASP.Net Web API 2 project with ADFS cookie authentication and hosted it on IIS. All works fine.
However, some clients have got old cookies which became invalid because of configuration changes. Such cookies cause following error when calling my API:
[CryptographicException: Key not valid for use in specified state.
]
System.Security.Cryptography.ProtectedData.Unprotect(Byte[] encryptedData, Byte[] optionalEntropy, DataProtectionScope scope) +447
System.IdentityModel.ProtectedDataCookieTransform.Decode(Byte[] encoded) +49
[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. ]
System.IdentityModel.ProtectedDataCookieTransform.Decode(Byte[] encoded) +329
System.IdentityModel.Tokens.SessionSecurityTokenHandler.ApplyTransforms(Byte[] cookie, Boolean outbound) +167
System.IdentityModel.Tokens.SessionSecurityTokenHandler.ReadToken(XmlReader reader, SecurityTokenResolver tokenResolver) +826
System.IdentityModel.Tokens.SessionSecurityTokenHandler.ReadToken(Byte[] token, SecurityTokenResolver tokenResolver) +92
System.IdentityModel.Services.SessionAuthenticationModule.ReadSessionTokenFromCookie(Byte[] sessionCookie) +569
System.IdentityModel.Services.SessionAuthenticationModule.TryReadSessionTokenFromCookie(SessionSecurityToken& sessionToken) +306
System.IdentityModel.Services.SessionAuthenticationModule.OnAuthenticateRequest(Object sender, EventArgs eventArgs) +159
System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +142
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +92
The obvious workaround is to clear the cookies. However, it's likely that I'll change the cookies configuration again in future, so I'd like to clear all invalid cookies automatically from the API.
I've tried adding a custom OWIN middleware and overriding IExceptionHandler
.
Here's my WIF config:
<system.identityModel>
<identityConfiguration>
<audienceUris>
<add value="https://my.web-api.com" />
</audienceUris>
<issuerNameRegistry type="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, System.IdentityModel.Tokens.ValidatingIssuerNameRegistry">
<authority name="ADFS">
<keys>
<add thumbprint="--a thumbprint--" />
</keys>
<validIssuers>
<add name="http://my.adfs.com/adfs/services/trust" />
</validIssuers>
</authority>
</issuerNameRegistry>
</identityConfiguration>
</system.identityModel>
<system.identityModel.services>
<federationConfiguration>
<wsFederation issuer="https://my.adfs.com/adfs/ls" realm="https://my.web-api.com" requireHttps="true" passiveRedirectEnabled="false"
persistentCookiesOnPassiveRedirects="true" />
<cookieHandler name="my.cookie" path="/" persistentSessionLifetime="7.0:0:0" />
<serviceCertificate>
<certificateReference x509FindType="FindBySubjectName" findValue="my.web-api.com" storeLocation="LocalMachine" storeName="My" />
</serviceCertificate>
</federationConfiguration>
</system.identityModel.services>
Here's my Startup
class:
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
var config = new HttpConfiguration();
config.Services.Replace(typeof(IExceptionHandler), new CryptographicExceptionHandler());
WebApiConfig.Register(config);
appBuilder.UseWebApi(config);
appBuilder.Use<ClearInvalidCookiesMiddleware>();
}
}
No matter what's inside CryptographicExceptionHandler
and ClearInvalidCookiesMiddleware
, their code is not called and I'm getting 500 error. I also tried to move ClearInvalidCookiesMiddleware
before UseWebApi
.
My aim is to add Set-Cookie
response header to clear invalid cookies and return 401 or a redirect.
How can I make OWIN to customize the response in this case?
The solution appeared to override SessionAuthenticationModule.OnAuthenticateRequest
and call SignOut()
in case of exceptions:
class ClearInvalidCookiesSessionAuthenticationModule : SessionAuthenticationModule
{
protected override void OnAuthenticateRequest(object sender, EventArgs eventArgs)
{
try
{
base.OnAuthenticateRequest(sender, eventArgs);
}
catch(InvalidOperationException ex) when (ex.InnerException is CryptographicException) // Invalid cookie signing key
{
SignOut();
}
catch(System.Xml.XmlException) // Invalid cookie structure
{
SignOut();
}
}
}
To use the inherited class instead of default one, one should insert following line inside Web.config:
<system.webServer>
<modules ...>
<!-- Insert the line below or replace existing SessionAuthenticationModule -->
<add name="SessionAuthenticationModule" preCondition="managedHandler"
type="MyNamespace.ClearInvalidCookiesSessionAuthenticationModule, MyAssembly" />
...
</modules>
...
</system.webServer>