WIF: CryptographicException: Key not valid for use in specified state | solve using matching machine keys
I had a customer who has a claim aware ASP.NET web application hosted on two IIS servers behind a load balancer.
Scenario:
They had a requirement where they would want to stop (bring down) server1 to perform some patch or maintenance activity. However, they did not want the users are being served by the affected server (server1) and use the same cookies that are set by server1 so that the load balancer would redirect the request to server2 and work seamlessly.
For testing this, we made a request to the web app from a client machine. We know whether the requested reached either Server1 or Server2 from IIS logs. Let say the request was served by Server1. We manually went to the IIS sever1 and stopped the application corresponding to the application.
On the client, when navigated to a different page with logged in session, LB identified the Server1 is down and redirected the request to server2.
Then, we ran into the following exception:
CryptographicException: Key not valid for use in specified state.
System.Security.Cryptography.ProtectedData.Unprotect(Byte[] encryptedData, Byte[] optionalEntropy, DataProtectionScope scope) +436
System.IdentityModel.ProtectedDataCookieTransform.Decode(Byte[] encoded) +48
[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) +319
System.IdentityModel.Tokens.SessionSecurityTokenHandler.ApplyTransforms(Byte[] cookie, Boolean outbound) +167
System.IdentityModel.Tokens.SessionSecurityTokenHandler.ReadToken(XmlReader reader, SecurityTokenResolver tokenResolver) +784
System.IdentityModel.Tokens.SessionSecurityTokenHandler.ReadToken(Byte[] token, SecurityTokenResolver tokenResolver) +88
System.IdentityModel.Services.SessionAuthenticationModule.ReadSessionTokenFromCookie(Byte[] sessionCookie) +512
System.IdentityModel.Services.SessionAuthenticationModule.TryReadSessionTokenFromCookie(SessionSecurityToken& sessionToken) +258
System.IdentityModel.Services.SessionAuthenticationModule.OnAuthenticateRequest(Object sender, EventArgs eventArgs) +143
General Approach:
We have a couple of blogs with detailed explanation on this issue and how to resolve it
- https://blogs.msdn.microsoft.com/distributedservices/2012/10/29/wif-1-0-id1073-a-cryptographicexception-occurred-when-attempting-to-decrypt-the-cookie-using-the-protecteddata-api/
- https://blogs.msdn.microsoft.com/napegadie_kones_msft_blog/2016/01/19/cryptographicexception-key-not-valid-for-use-in-specified-state/
This exception usually occurs, when there is a load balancer that routes incoming user requests to one of the servers in a cluster environment. In other words, the exception will occur if the request gets routed to a different server than the one that served the first request for that session.
There are three ways to resolve this issue as mentioned in the above blogs:
1- Use sticky sessions at the Load Balancer
2- Use X509 Certificate to secure sessions cookies
3- Use DPAPI with matching machine keys
Challenges:
In our scenario, enabling sticky sessions will not help. And we don’t want to use certificate since it needs some code changes. We are only left with option 3 where use matching machine keys across the machines in the cluster for encrypting and decrypting session cookies.
Though we generate machine keys from one IIS machine and copied to all the servers in the load balancer, we might still see it is failing with the same error.
By adding matching Machines keys to the web.cofig or machine.config will not suffice.
WIF provides a specialized session token handler, the MachineKeySessionSecurityTokenHandler, that protects tokens by using the keys specified in the <machineKey> element.
Configure your application to use MachineKeySessionSecurityTokenHandler by adding it to the token handler collection. Remove SessionSecurityTokenHandler , and add MachineKeySessionSecurityTokenHandler .
I added the following in config:
<securityTokenHandlers>
<remove type="SessionSecurityTokenHandler"/>
<add type="MachineKeySessionSecurityTokenHandler" />
</securityTokenHandlers>
With the above config, ran into configuration errors that it could not find the specified handler, and the site itself doesn’t load.
Then, I modified the config the following way:
<securityTokenHandlers>
<clear/>
<remove type="System.IdentityModel.Tokens.SessionSecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<add type="System.IdentityModel.Services.Tokens.MachineKeySessionSecurityTokenHandler, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</securityTokenHandlers>
Then, I ran into the following error:
ID4014: A SecurityTokenHandler is not registered to read security token ('Assertion', 'urn:oasis:names:tc:SAML:2.0:assertion').
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.IdentityModel.Tokens.SecurityTokenException: ID4014: A SecurityTokenHandler is not registered to read security token ('Assertion', 'urn:oasis:names:tc:SAML:2.0:assertion').
Source Error:
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.
Stack Trace:
[SecurityTokenException: ID4014: A SecurityTokenHandler is not registered to read security token ('Assertion', 'urn:oasis:names:tc:SAML:2.0:assertion').]
System.IdentityModel.Services.TokenReceiver.ReadToken(String tokenXml, XmlDictionaryReaderQuotas readerQuotas, FederationConfiguration federationConfiguration) +440
System.IdentityModel.Services.WSFederationAuthenticationModule.SignInWithResponseMessage(HttpRequestBase request) +337
System.IdentityModel.Services.WSFederationAuthenticationModule.OnAuthenticateRequest(Object sender, EventArgs args) +467
System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +139
System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step) +195
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +88
Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.7.2623.0
Solution:
While I edited the config using visual studio editor, <clear/> tag got added within section. And encountered the above error. Make sure tag is removed.
The right config element that worked is the following:
<System.IdentityModel>
<identityConfiguration>
<securityTokenHandlers>
<remove type="System.IdentityModel.Tokens.SessionSecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<add type="System.IdentityModel.Services.Tokens.MachineKeySessionSecurityTokenHandler, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</securityTokenHandlers>
</identityConfiguration>
</System.IdentityModel>
Hope this helps!