IDX10503: Signature validation failed. Token does not have a kid. Keys tried: 'System.Text.StringBuilder'
Asked Answered
S

6

20

I have the below JWT token,

eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJjbGllbnRpZCIsImF1ZCI6ImNsaWVudGlkIiwic3ViIjoiMTIzIiwiYSI6IjQ1NiIsImlhdCI6MTYyMTc5OTU5OCwiZXhwIjoxNjIxNzk5NjU4fQ.hglbX63zhPwTOsB-zSiOMfxEKl5OaIk6zX1o9-LEhP3nro8fa5_3QyIH7I5971j-xuO1bccX1TOh0kNcQ-ACAg

Which is generated using,

    public static string GenerateToken(string key, string a1, string a2)
    {
        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
        var token = new JwtSecurityToken(
            claims: new Claim[]
            {
            new Claim(JwtRegisteredClaimNames.Iss, "clientid"),
            new Claim(JwtRegisteredClaimNames.Aud, "clientid"),
            new Claim(JwtRegisteredClaimNames.Sub, a1),
            new Claim("a", a2),
            new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64),
            },
            //notBefore: new DateTimeOffset(DateTime.Now).DateTime,
            expires: new DateTimeOffset(DateTime.Now.AddMinutes(1)).DateTime,
            signingCredentials: new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha512)
        );

        return new JwtSecurityTokenHandler().WriteToken(token);
    }

var key = "Ym7AD3OT2kpuIRcVAXCweYhV64B0Oi9ETAO6XRbqB8LDL3tF4bMk9x/59PljcGbP5v38BSzCjD1VTwuO6iWA8uzDVAjw2fMNfcT2/LyRlMOsynblo3envlivtgHnKkZj6HqRrG5ltgwy5NsCQ7WwwYPkldhLTF+wUYAnq28+QnU=";
// Key is test                
var token = GenerateToken(key, "123", "456");

After getting token I am validating using below code,

var key = "Ym7AD3OT2kpuIRcVAXCweYhV64B0Oi9ETAO6XRbqB8LDL3tF4bMk9x/59PljcGbP5v38BSzCjD1VTwuO6iWA8uzDVAjw2fMNfcT2/LyRlMOsynblo3envlivtgHnKkZj6HqRrG5ltgwy5NsCQ7WwwYPkldhLTF+wUYAnq28+QnU=";
// key is test

var hmac = new HMACSHA512(Convert.FromBase64String(key));
var validationParameters = new TokenValidationParameters
            {
                ValidAudience = "clientid",
                ValidIssuer = "clientid",
                IssuerSigningKey = new SymmetricSecurityKey(hmac.Key)
            };
            var tokenHandler = new JwtSecurityTokenHandler();
            return tokenHandler.ValidateToken(token, validationParameters, out var validToken);

But I am getting below error,

IDX10503: Signature validation failed. Token does not have a kid. Keys tried: 'System.Text.StringBuilder'.
Exceptions caught:
 'System.Text.StringBuilder'.
token: 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken'.
Scotland answered 23/5, 2021 at 20:1 Comment(4)
jwt.io saying signature verifiedScotland
try again. it tells incorrect signaturePenney
No it is verified.Scotland
I found the issue. Used Convert.FromBase64String instead of Encoding.UTF8.GetBytesScotland
S
20

The issue was this line,

var hmac = new HMACSHA512(Convert.FromBase64String(key));

I changed it to,

var hmac = new HMACSHA512(Encoding.UTF8.GetBytes(key));

The error is misleading. Soure code for the error is at https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/d6f2b66d788195b50f2b1f700beb497851194c73/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs#L1016

Scotland answered 24/5, 2021 at 10:16 Comment(1)
Well done. Error is thrown any time string.IsNullOrEmpty(jwtToken.Header.Kid); happens and keysAttempted.Length > 0, which is misleading.Antispasmodic
C
15

In addition to being able to use a base64 encoded key value as demonstrated above, you can also use a string. There are some caveats though.

The handlers that use a SymmetricSecurityKey to create a HMACSHA256 or HMACSHA512, don't perform the zero-bit padding out to the hashsize (As per RFC2104, Step 1), which causes the signing algorithm to fail. Everything works fine if the string which is used as the key is greater than the keysize (256 or 512 bit).

Below are two ways to generate an appropriate SymmetricSecurityKey for a shorter signing string:

First Approach: Manually pad the string with zero bytes and generate a base64 key. Something like CyberChef could be used to append /0 chars out to either 32 chars for 256 bit or 64 chars for 512 bit:

Manually generating a base64 key via CyberChef

These keys will produce an identical JWT

Equivalent keys in JWT.io

This can be used as demonstrated by Imran above...

// Set up the signingKey for HS256
// Base64 signingKey
//SymmetricSecurityKey signingKey = new SymmetricSecurityKey(Convert.FromBase64String("c2VjcmV0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="));

Second Approach: Without doing the base64 conversion, the SymmetricSecurityKey will take an ascii byte[], so long as you have already done the padding:

// Short String signingKey
// Note: Not advised. Short keys can be bruteforced, allowing tokens to be forged. 
// Note: manually padding to 256 bits if it is a short key, as the SymmetricSignatureProvider does not do the HMACSHA256 RFC2104 padding for you.
// SymmetricSecurityKey signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("secret".PadRight((256/8), '\0')));
SymmetricSecurityKey signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("secret".PadRight((512/8), '\0')));

With either approach, you can use the resulting SymmetricSecurityKey in the default JwtBearer handler:

builder.Services.AddAuthentication(options => {
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options => {
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateAudience = true,
        ValidateIssuer = true,
        ValidIssuer = "https://localhost:7046/",
        ValidAudience = "https://localhost:7046/",
        RequireSignedTokens = true,
        IssuerSigningKey = signingKey,
        ValidateLifetime = true
    };
});

builder.Services.AddAuthorization(auth =>
            {
                auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
                        .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme‌​)
                    .RequireAuthenticatedUser().Build());
            });

As a final note, I had some issues in one test where I had added the Microsoft.IdentityModel.Tokens package. I have still to run this down, but there may be a conflicting class in that package...

I have a sample using this approach here: Example project in GitHub

It is worth noting that using a short symmetric key for your HMAC is not advised, as the secret can be bruteforced with relative ease, especially if it show up in a known password list. Worth checking your key past HaveIBeenPwned - PwnedPasswords to have some confidence of whether it has been used before. This is mainly provided for people creating demonstrations of short HMAC's. If you are uncomfortable with using PwnedPasswords, see the following about the K-anonymity model they use, and experiment while watching your network tab in browser tools to see how it works.

Cavorilievo answered 26/3, 2022 at 3:11 Comment(1)
This line worked for me. ` SymmetricSecurityKey signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("secret".PadRight((512/8), '\0'))); ` Thanks a lot.Firooc
M
5

This is a pretty generic error with regards to debugging. What's basically happening:

  1. You tried to sign in to the api
  2. You got redirected to IdentityServer to log in.
  3. You succesfully logged in, IdentityServer gives you a token.
  4. You try to log in to the api with your token
  5. The api tries to verify your token.

The error is telling you the api tried to verify the token, but failed. The "kid" the api is complaining about is the key ID of whatever secret it used to verify the token. The kid is probably a thumbprint of the certificate used by IdentityServer as a signing credential. To verify your token, your api will call IdentityServer independently, and retrieve the needed data to verify it. Specifically, your api will try to go to youridentityserver.com/.well-known/openid-configuration/jwks . There, it should find the kid with public key to verify the token.

It helps to find the cause of your issue if you enable PII in the application throwing the error by adding this to Startup.ConfigureServices:

IdentityModelEventSource.ShowPII = true;

This will show you:

  • The kid of the value the api tried to validate the passed in token with.
  • The token itself, which in my case also includes a kid, in my case the thumbprint of the signing credential certificate used (if you used a certificate).

Using this information, you can hopefully find the cause. Things to check:

  • Is the IdentityServer that gave you the token the same one that the api is calling to get the kid from? If you have multiple IdentityServers behind a load balancer you may run into issues.
  • Does the JWKS endpoint on all IdentityServer instances return the same kid? If you are using different certificates on different machines, or don't have the certificate installed, or the account running IdentityServer doesn't have read rights to the certificate, you may run into issues.

More info here: https://docs.wso2.com/display/IS540/JSON+Web+Key+Set+Endpoint This link is for a different identity provider, but IdentityServer returns the same type of data.

enter image description here

Mishear answered 13/6, 2023 at 14:20 Comment(0)
S
1

I receive same error message, but my issue was different. Problem was with this line:

new Ed25519PublicKeyParameters(Encoding.UTF8.GetBytes(key), 0);

So I change to this:

new Ed25519PublicKeyParameters(Convert.FromBase64String(key), 0);

Key was saved to file as base64, but I was loading it as UTF8 bytes.

Sparkman answered 24/1, 2022 at 22:20 Comment(1)
Yeah it is the same issue that I got but in your case it was other way around, in other words it is encoding bytes issue :)Scotland
P
0

I had this same error message and the underlying cause for me was that the JWT key was mismatched between the API project and the web/client project. this should be obvious but they appeared to be the same based on the appsettings.json and web.config files, however the API project was loading an alternative dev/staging/prod configuration from a publish profile. As soon as the keys were identical the code worked as expected. I use UTF8.GetBytes for the key on server + client and it works fine.

Pederast answered 31/1, 2023 at 12:8 Comment(0)
T
0

In my case, I was using a random string as JWT signing key in app setting, later I simply updated it with encoded Base 64 format in app setting only.

//in app settings json
Jwt:Key = {encoded base 64 string}



// in generating token  & validating token section
var key = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(configuration["Jwt:Key"]));
        var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new[] { new Claim("Name", user.UserName) }),
                Expires = DateTime.UtcNow.AddMinutes(double.Parse(configuration["Token:ExpiryTimeInMinutes"])),
                SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256Signature),
                Issuer = configuration["Jwt:Issuer"],
                Audience = configuration["Jwt:Audience"]
            };
Thermal answered 25/5, 2023 at 18:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.