CryptographicException: Key not valid for use in specified state.
Context:
You may see the following exception when accessing several claims based Web applications behind a load balancer either in .NET 3.5, .NET 4.0 or .NET 4.5:
[CryptographicException: Key not valid for use in specified state.
]
System.Security.Cryptography.ProtectedData.Unprotect(Byte[] encryptedData, Byte[] optionalEntropy, DataProtectionScope scope) +397
System.IdentityModel.ProtectedDataCookieTransform.Decode(Byte[] encoded) +90
[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) +1158374
System.IdentityModel.Tokens.SessionSecurityTokenHandler.ApplyTransforms(Byte[] cookie, Boolean outbound) +173
System.IdentityModel.Tokens.SessionSecurityTokenHandler.ReadToken(XmlReader reader, SecurityTokenResolver tokenResolver) +756
System.IdentityModel.Tokens.SessionSecurityTokenHandler.ReadToken(Byte[] token, SecurityTokenResolver tokenResolver) +100
System.IdentityModel.Services.SessionAuthenticationModule.ReadSessionTokenFromCookie(Byte[] sessionCookie) +668
System.IdentityModel.Services.SessionAuthenticationModule.TryReadSessionTokenFromCookie(SessionSecurityToken& sessionToken) +164
System.IdentityModel.Services.SessionAuthenticationModule.OnAuthenticateRequest(Object sender, EventArgs eventArgs) +173
System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +92
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +165
NB:
A great explanation of the issue was provided in the following blog: A CryptographicException occurred when attempting to decrypt the cookie using the ProtectedData API
However, I feel like some parts need some additional info.
Resolution:
This exception usually occurs when there is a load balancer that routes incoming user requests to one of the servers in a cluster environment.
By default, DPAPI is used to secure the user session cookie.
Therefore, the exception will occur if the request gets routed to a different server than the one that serviced the first request for that session.
There are three ways to resolved this issue:
1- Use sticky sessions at the Load Balancer
2- Use X509 Certificate to secure sessions cookies
Below is the way to do that:
For framework 4.5:
==============
C#:
void OnFederationConfigurationCreated(object sender, FederationConfigurationCreatedEventArgs e)
{
var sessionTransforms = new List<CookieTransform>(new CookieTransform[]
{
new DeflateCookieTransform(),
new RsaEncryptionCookieTransform(e.FederationConfiguration.ServiceCertificate),
new RsaSignatureCookieTransform(e.FederationConfiguration.ServiceCertificate)
});
var sessionHandler = new SessionSecurityTokenHandler(sessionTransforms.AsReadOnly());
e.FederationConfiguration
.IdentityConfiguration
.SecurityTokenHandlers
.AddOrReplace(sessionHandler);
}
Config:
<system.identityModel.services>
<federationConfiguration>
--------
<serviceCertificate>
<certificateReference x509FindType="FindByThumbprint" findValue="[yourCorrespondingThumbprint]"/>
</serviceCertificate>
</federationConfiguration>
</system.identityModel.services>
For framework 4.0 and 3.5:
==========================
C#:
void OnServiceConfigurationCreated(object sender, ServiceConfigurationCreatedEventArgs e)
{
var sessionTransforms = new List<CookieTransform>(new CookieTransform[]
{
new DeflateCookieTransform(),
new RsaEncryptionCookieTransform(
e.ServiceConfiguration.ServiceCertificate),
new RsaSignatureCookieTransform(
e.ServiceConfiguration.ServiceCertificate)
});
var readOnlyTransforms = sessionTransforms.AsReadOnly();
var sessionHandler = new SessionSecurityTokenHandler(readOnlyTransforms);
e.ServiceConfiguration.SecurityTokenHandlers.AddOrReplace(sessionHandler);
}
Config:
<microsoft.identityModel>
<service>
...
<serviceCertificate>
<certificateReference x509FindType="FindByThumbprint" findValue="[yourCorrespondingThumbprint]"/>
</serviceCertificate>
</service>
</microsoft.identityModel>
NB: certificate has to be the same across all the servers. That is, we should have the same yourCorrespondingThumbprint across all the servers behind LB
3- Use DPAPI with matching machine keys
In this case, you can generate the machine key in IIS as follows:
Then, Add the following entry in </system.web> of C:\Windows\Microsoft.NET\Framework64\<Version>\Config\Machine.Config:
<machineKey decryption="Auto" decryptionKey="BA43FB8C9792E9D35F98942B429611584F682394E65DC452" validationKey=" 55941848BB4B57BB9E9D882DD3DF9D2EA4CC174631132832D3780F42A09B9BBA8EC82E92693AC2B13F2E99A5D48C637BA47301366CF622DCA9B0AD3E18E26DC1