title: JwtSecurityTokenHandler says signature of JWT valid after changing 1 char
body: <p>We are trying to validate the ID Token (IDT) presented to a .NET client application by an OpenID Connect Provider (OP). The IDT is what you would expect. Nothing unusual going on there.</p>
  
  <p>To verify the signature of the IDT, we can get the exponent and modulus from the OP by calling a public endpoint. These can be used to create a public key that corresponds to the private one used by the OP to sign the IDT. With these, we create a RSACryptoServiceProvider object to do the signature verification. To help with this, we are are passing the crypto service provider as a token validation parameter to a JwtSecurityTokenHandler. </p>
  
  <p>This works fine. We thought we were done and ready for the weekend. However, we found that <strong>we can change the last character in the signature and the JwtSecurityTokenHandler will still tell us the JWT is valid</strong>. We cannot find an explanation for this and are wondering if:</p>
  
  <ol>
  <li>It is a problem with the way we are creating the signing key which is causing it to improperly validate JWTs.</li>
  <li>There is a bug in the JwtSecurityTokenHandler.</li>
  <li>We are not understanding the spec completely and this small alteration is allowed because the last character in the signature part of the JWT is actually not pertinent for verification.</li>
  <li>Something else</li>
  </ol>
  
  <p>We are using System.IdentityModel.Tokens.JwtSecurityTokenHandler from System.IdentityModel.Tokens.Jwt.dll v4.0.30319.</p>
  
  <p>A very simple sample of our code is below.</p>
  
  <h1>Program.cs</h1>
  
  <pre class="lang-c# prettyprint-override"><code>using System;
  using System.Configuration;
  using System.IdentityModel.Tokens;
  using System.Text;
  
  namespace ConsoleApplication1
  {
      class Program
      {
          static void Main(string[] args)
          {
              var token = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJzdWIiOiJ1c2VyMSIsImF1ZCI6ImNsaWVudDEiLCJqdGkiOiJKcUFDVVFiTlRQR201U0ZJRXY3MWR0IiwiaXNzIjoiaHR0cHM6XC9cL2xvY2FsaG9zdDo5MDMxIiwiaWF0IjoxNDEzNTcwNjEyLCJleHAiOjE0MTM1NzA5MTJ9.Z3P4Rt_w7d0oP8x6zfaot8PIxpEJHUw43Z_4VkOzv59nRz1dWopGUXw51DJd5cLjeM_zc14durs5NhJE27WmcKaEuE8-WZ0ubxM_bzykZfmAPa1WVk9KctPKiUH7QZg4OCLaqIX6usi5kkuICiPVdoJPkHmojMkm5nCqeBIbYteasysMTQGq93VtoBGUQomF89ZaFMBlUy0ofH7SEKJEW_4vgy7Umu0h7kNKkh6Aw4x9Bw1AkG1D6H_scsuH2uSxQ7QV-3G60DcjLZ31_R1ZxaUg2WS2ajemb6swKM4LIOR9_mK6ScUVVBxBL4Oh9g6EA93lMg_1GRZi780v_3TR8Q";
  
              var tokenValidator = new TokenValidator(new CacheProvider(), new DebugOpenIdConnectProviderClient(), 
                  ConfigurationManager.AppSettings["AUDIENCE"], ConfigurationManager.AppSettings["ISSUER"]);
              SecurityToken securityToken;
              var principal = tokenValidator.Validate(token, out securityToken);
  
              if (principal != null)
              {
                  Console.Out.WriteLine("Security token is valid");
              }
  
              foreach (var claim in principal.Claims)
              {
                  Console.Out.WriteLine("{0} = {1}", claim.Type, claim.Value);
              }
  
              Console.ReadLine();
          }
      }
  }
  </code></pre>
  
  <h1>TokenValidator.cs</h1>
  
  <pre class="lang-c# prettyprint-override"><code>using System;
  using System.Collections.Generic;
  using System.IdentityModel.Tokens;
  using System.Security.Claims;
  using System.Security.Cryptography;
  using Newtonsoft.Json;
  
  namespace ConsoleApplication1
  {
      public class TokenValidator
      {
          private readonly CacheProvider cacheProvider;
          private readonly IOpenIdConnectProviderClient openIdConnectProviderClient;
          private readonly string audience;
          private readonly string issuer;
  
          public TokenValidator(CacheProvider cacheProvider, IOpenIdConnectProviderClient openIdConnectProviderClient, string audience, string issuer)
          {
              this.cacheProvider = cacheProvider;
              this.openIdConnectProviderClient = openIdConnectProviderClient;
              this.audience = audience;
              this.issuer = issuer;
          }
  
          public ClaimsPrincipal Validate(string tokenString, out SecurityToken securityToken)
          {
              var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
              var jwt = jwtSecurityTokenHandler.ReadToken(tokenString) as JwtSecurityToken;
              var publicKey = GetPublicKey(jwt.Header.SigningKeyIdentifier[0].Id);
              var rsaPublicKey = CreatePublicKey(publicKey.n, publicKey.e);
  
              return jwtSecurityTokenHandler.ValidateToken(tokenString, new TokenValidationParameters()
              {
                  IssuerSigningToken = new RsaSecurityToken(rsaPublicKey, publicKey.kid),
                  IssuerSigningKeyResolver = (token, securityToken2, keyIdentifier, validationParameters) =&gt; {
                      return new RsaSecurityKey(rsaPublicKey);
                  },
  #if DEBUG
                  ClockSkew = new TimeSpan(0, 30, 0),
  #endif
                  ValidIssuer = issuer,
                  ValidAudience = audience,
              }, out securityToken);
          }
  
          public static RSACryptoServiceProvider CreatePublicKey(string modulus, string exponent)
          {
              var cryptoProvider = new RSACryptoServiceProvider();
  
              cryptoProvider.ImportParameters(new RSAParameters()
              {
                  Exponent = Base64UrlEncoder.DecodeBytes(exponent),
                  Modulus = Base64UrlEncoder.DecodeBytes(modulus),
              });
  
              return cryptoProvider;
          }
  
          private PublicKeyData GetPublicKey(string kid)
          {
              var keys = cacheProvider["PUBLIC_KEYS"] as Dictionary&lt;string, PublicKeyData&gt;;
  
              if (keys == null)
              {
                  keys = GetPublicKeysFromPingFederate();
  
                  cacheProvider["PUBLIC_KEYS"] = keys;
              }
  
              var currentKey = keys[kid];
  
              if (currentKey != null)
              {
                  return currentKey;
              }
  
              throw new Exception("Could not find public key for kid: " + kid);
          }
  
          private Dictionary&lt;string, PublicKeyData&gt; GetPublicKeysFromPingFederate()
          {
              var keyString = openIdConnectProviderClient.Execute();            
              var keys = JsonConvert.DeserializeObject&lt;PublicKeysJsonResult&gt;(keyString);
              var result = new Dictionary&lt;string, PublicKeyData&gt;();
  
              foreach (var key in keys.Keys)
              {
                  result[key.kid] = key;
              }
  
              return result;            
          }
      }
  }
  </code></pre>