Reputation: 418
So this whole JWT signing and validation is quite new to me. I now have an C# application which is requesting some information via an API secured with JWT. Weird thing is that every other request fails. So the first request works like a charm. I'm getting the info and responses I expect. JWT validation is successful.
The next request i do after it (starting the whole process form start. inclusive getting a new accesstoken since the refreshtoken is not supported) I get an 'idx10503 signature validation failed. token does not have a kid'. I can't get my head around it. The JWT.io debugger says the signature is valid.
If after the failed validation I do a new request (again starting the whole process) the JWT is valid.
So, to make it clear, it looks like this:
The part where I validate my JWT and get the error is below:
try
{
var keyBytes = Convert.FromBase64String(publicKey);
AsymmetricKeyParameter asymmetricKeyParameter = PublicKeyFactory.CreateKey(keyBytes);
RsaKeyParameters rsaKeyParameters = (RsaKeyParameters)asymmetricKeyParameter;
RSAParameters rsaParameters = new RSAParameters
{
Modulus = rsaKeyParameters.Modulus.ToByteArrayUnsigned(),
Exponent = rsaKeyParameters.Exponent.ToByteArrayUnsigned()
};
using (var rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(rsaParameters);
var validationParameters = new TokenValidationParameters()
{
RequireExpirationTime = true,
RequireSignedTokens = true,
ValidateAudience = false,
ValidateIssuer = false,
IssuerSigningKey = new RsaSecurityKey(rsa)
};
var handler = new JwtSecurityTokenHandler();
handler.ValidateToken(jwtToken, validationParameters, out var validatedToken);
}
return validatedToken;
}
catch (Exception e)
{
throw e;
}
I have already tried to see if it makes a difference if I put the RSAParameters in the cache and use those same parameters in the next request. Unfortunatly that makes it worse in my case because all the next JWT validations fail.
Does anyone have an idea what might go wrong?
Upvotes: 8
Views: 9240
Reputation: 2468
If you're getting this error on every other request, like the OP mentioned, you're probably getting hit by a very tricky little internal caching issue (details below). Your options are:
CryptoProviderFactory.Default.CacheSignatureProviders = false
in a spot where it'll run before any other crypto stuff is done.RSACryptoServiceProvider
, nor the object returned from RSA.Create()
, as your case may be. (I don't love this idea because it feels like it'd be leaky. I also don't like the look of trying to reuse these instances because, in my case, they're getting loaded with state that varies across usages.)From the docs:
Caching in Microsoft.IdentityModel
...IMPORTANT NOTES: When creating a signature provider with CryptoProviderFactory.CacheSignatureProviders = true, it is important not to dispose of the keying material associated with that SignatureProvider while it is still in the cache.
Detailed discussions on github of what's going on are here and here.
Upvotes: 0
Reputation: 31
For anyone else who has this issue, it was because I had updated my Microsoft.EntityFrameworkCore.SqlServer Nuget package from v.5.0.6 to v.6.0.7 which updated Microsoft.Data.Sqlclient from v2.0.1 to v2.1.4 which in turn updated System.Identity.Model from v4.14.0 to to v4.21.1. Rolling back to Microsoft.EntityFrameworkCore.SqlServer v.5.0.6 removed the issue. It looks like System.Identity.Model is updated in EntityFrameworkCore >= v6.0
The lesson here is to always read what Nuget is telling you when installing packages!
Upvotes: 3
Reputation: 330
I solved my problem which was the same error message as the OP had. It turns out I was using the wrong certificate. The error message is extremely misleading. If the "kid" field is non-existent and you're using the wrong cert, you'll get that incorrect IDX10503 error. I looked at the .net source code and it uses the x5t field if the kid field is missing. Also, I didn't implement OP's fix but it still worked.
Upvotes: 1
Reputation: 418
Fixed my issue finally by adding some parameters to the validationParameters;
TryAllIssuerSigningKeys = true,
IssuerSigningKey = new RsaSecurityKey(rsa),
IssuerSigningKeys = new List<SecurityKey>() { new RsaSecurityKey(rsa) }
Upvotes: 6