After updating the Microsoft.IdentityModel.Tokens library we were getting the following error message when creating JWT tokens:
System.InvalidOperationException
HResult=0x80131509
Message=IDX10638: Cannot created the SignatureProvider, ‘key.HasPrivateKey’ is false, cannot create signatures. Key: Microsoft.IdentityModel.Tokens.RsaSecurityKey.
Source=Microsoft.IdentityModel.Tokens
StackTrace:
at Microsoft.IdentityModel.Tokens.AsymmetricSignatureProvider..ctor(SecurityKey key, String algorithm, Boolean willCreateSignatures)
at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateSignatureProvider(SecurityKey key, String algorithm, Boolean willCreateSignatures)
at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForSigning(SecurityKey key, String algorithm)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.CreateEncodedSignature(String input, SigningCredentials signingCredentials)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.WriteToken(SecurityToken token)
The code where this happened looked like this and was working fine before the update:
var buffer = Convert.FromBase64String(Base64Cert);
var signingCertificate = new X509Certificate2(buffer, CertificatePassword);
var identity = new ClaimsIdentity(Claims);
var data = new AuthenticationTicket(identity, null);
if (signingCertificate.PrivateKey is RSACryptoServiceProvider rsaProvider)
{
var key = new RsaSecurityKey(rsaProvider);
var signingCredentials = new SigningCredentials(key, SecurityAlgorithms.RsaSha256Signature);
var token = new JwtSecurityToken(
issuer: TokenIssuer,
audience: TokenAudience,
claims: data.Identity.Claims,
notBefore: DateTime.UtcNow,
expires: DateTime.UtcNow.AddMinutes(TokenValidityInMinutes),
signingCredentials: signingCredentials
);
var tokenString = new JwtSecurityTokenHandler().WriteToken(token);
Console.WriteLine(tokenString);
}
else
{
Console.Error.WriteLine("signingCertificate.PrivateKey is not an RSACryptoServiceProvider");
}
Debugging the code, I saw that signingCertificate.HasPrivateKey was true but key.HasPrivateKey was false
In order to solve it, two small changes were required:
- Add a keyStorageFlags parameter to the X509Certificate2 constructor so that the imported keys are marked as exportable
- Use the ExportParameters method to retrieve the raw RSA key in the form of an RSAParameters structure including private parameters.
So using the following code worked without exception:
var buffer = Convert.FromBase64String(Base64Cert);
var signingCertificate = new X509Certificate2(buffer, CertificatePassword, X509KeyStorageFlags.Exportable);
var identity = new ClaimsIdentity(Claims);
var data = new AuthenticationTicket(identity, null);
if (signingCertificate.PrivateKey is RSACryptoServiceProvider rsaProvider)
{
var key = new RsaSecurityKey(rsaProvider.ExportParameters(true));
var signingCredentials = new SigningCredentials(key, SecurityAlgorithms.RsaSha256Signature);
var token = new JwtSecurityToken(
issuer: TokenIssuer,
audience: TokenAudience,
claims: data.Identity.Claims,
notBefore: DateTime.UtcNow,
expires: DateTime.UtcNow.AddMinutes(TokenValidityInMinutes),
signingCredentials: signingCredentials
);
var tokenString = new JwtSecurityTokenHandler().WriteToken(token);
Console.WriteLine(tokenString);
}
else
{
Console.Error.WriteLine("signingCertificate.PrivateKey is not an RSACryptoServiceProvider");
}