title: Implementing JWT on .net core app throws invalid payload
body: <p>I'm trying to implement JWT Token between my .net core 2.2 API and .net core 3.1 Web-Application but having trouble after login with unprotecting the token.
  In debugging-mode (local computer) it works perfectly, after publishing it to my server I instantly running into various exceptions.</p>
  
  <p>First I got the exception <code>The key {...some GUID...} was not found in the keyring</code>. So I read and saw, I need to set <code>Load User Profile</code> for my application-pool to <code>true</code>. That's what I've done.</p>
  
  <p>Now my exception says</p>
  
  <pre><code>CryptographicException: The payload was invalid.
  Microsoft.AspNetCore.DataProtection.Cng.CbcAuthenticatedEncryptor.DecryptImpl(Byte* pbCiphertext, uint cbCiphertext, Byte* pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData)
  Microsoft.AspNetCore.DataProtection.Cng.Internal.CngAuthenticatedEncryptorBase.Decrypt(ArraySegment&lt;byte&gt; ciphertext, ArraySegment&lt;byte&gt; additionalAuthenticatedData)
  Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.UnprotectCore(byte[] protectedData, bool allowOperationsOnRevokedKeys, out UnprotectStatus status)
  Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.DangerousUnprotect(byte[] protectedData, bool ignoreRevocationErrors, out bool requiresMigration, out bool wasRevoked)
  Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.Unprotect(byte[] protectedData)
  KlehnPhotography.Site.TokenAuthentication.JwtTokenService.UnprotectToken(string protectedText) in JwtTokenValidator.cs
  KlehnPhotography.Site.Service.ServiceBase.CreateClient() in ServiceBase.cs
  KlehnPhotography.Site.Service.TagService.GetTags() in TagService.cs
  KlehnPhotography.Site.Controllers.PhotosController.Index() in PhotosController.cs
  lambda_method(Closure , object )
  Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable+Awaiter.GetResult()
  Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor+TaskOfActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, object controller, object[] arguments)
  System.Threading.Tasks.ValueTask&lt;TResult&gt;.get_Result()
  System.Runtime.CompilerServices.ValueTaskAwaiter&lt;TResult&gt;.GetResult()
  Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.&lt;InvokeActionMethodAsync&gt;g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask&lt;IActionResult&gt; actionResultValueTask)
  ....
  </code></pre>
  
  <p>I don't know where to start since locally it works fine - so debugging doesn't help.</p>
  
  <p>I guess the problem is with the website, not the API (correct me if I'm wrong), so here are my Startup.cs and my TokenAuthetication-namespace:</p>
  
  <p>Startup.cs:</p>
  
  <pre><code>public void ConfigureServices(IServiceCollection services)
  {
      services.AddSession(options =&gt;
                          {
                              options.IdleTimeout = TimeSpan.FromMinutes(30);
                              options.Cookie.HttpOnly = true;
                          });
  
      SecurityKey signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("mysupersecret_secretkey!123"));
  
      services.AddScoped&lt;IDataSerializer&lt;AuthenticationTicket&gt;, TicketSerializer&gt;();
  
      TokenValidationParameters tokenValidationParameters =
          new TokenValidationParameters
          {
              ValidateIssuerSigningKey = true,
              IssuerSigningKey = signingKey,
  
              ValidateIssuer = true,
              ValidIssuer = "ExampleIssuer",
  
              ValidateAudience = true,
              ValidAudience = "ExampleAudience",
  
              ValidateLifetime = true,
              ClockSkew = TimeSpan.Zero,
          };
  
      var serialiser = services.BuildServiceProvider().GetService&lt;IDataSerializer&lt;AuthenticationTicket&gt;&gt;();
      var dataProtector = services.BuildServiceProvider()
                                  .GetDataProtector(new[] {"IronSphere.Web.Site-Auth"});
  
      services.AddAuthentication(options =&gt;
                                  {
                                      options.DefaultAuthenticateScheme =
                                          CookieAuthenticationDefaults.AuthenticationScheme;
                                      options.DefaultChallengeScheme =
                                          CookieAuthenticationDefaults.AuthenticationScheme;
                                  })
              .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, 
                  options =&gt;
                      {
                          options.Cookie.Name = "access_token";
                          options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
                          options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
                          options.ReturnUrlParameter = "returnUrl";
                          options.TicketDataFormat = new JwtTokenValidator(SecurityAlgorithms.HmacSha256,
                                              tokenValidationParameters, serialiser, dataProtector);
                      });
  
      services.AddMvc();
  
      services.Configure&lt;AppSettings&gt;(Configuration.GetSection("AppSettings"));
  
      services.AddTransient(x =&gt; x.GetDataProtector(new[] {"IronSphere.Web.Site-Auth"}));
  
      services.AddTransient&lt;IAccountService, AccountService&gt;();
  
      services.AddHttpContextAccessor();
      services.AddOptions&lt;AppSettings&gt;();
      services.AddMemoryCache();
      services.TryAddSingleton&lt;IActionContextAccessor, ActionContextAccessor&gt;();
      services.AddTransient(typeof(ISession), serviceProvider =&gt;
      {
          var httpContextAccessor =
              serviceProvider.GetService&lt;IHttpContextAccessor&gt;();
          return httpContextAccessor.HttpContext.Session;
      });
      services.AddTransient(typeof(ISession), serviceProvider =&gt;
                                              {
                                                  var httpContextAccessor =
                                                      serviceProvider.GetService&lt;IHttpContextAccessor&gt;();
                                                  return httpContextAccessor.HttpContext.Session;
                                              });
      services.AddTransient&lt;IJwtTokenService, JwtTokenService&gt;();
  
      services.AddDataProtection()
          .SetApplicationName("KlehnPhotography.Site");
  }
  
  // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
  public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  {
      app.UseSession();
  
      app.UseStaticFiles();
      app.UseStaticFiles(new StaticFileOptions
      {
          FileProvider = new PhysicalFileProvider(Configuration.GetSection("AppSettings:ImagesPath").Value),
          RequestPath = "/images"
      });
  
      app.UseFileServer();
      app.UseRouting();
  
      app.UseAuthentication();
      app.UseAuthorization();
  
      app.UseEndpoints(endpoints =&gt;
      {
          endpoints.MapControllerRoute(
              name: "default",
              pattern: "{controller=Home}/{action=Index}/{id?}");
      });
  }
  </code></pre>
  
  <p>TokenValidator.cs</p>
  
  <pre><code>public interface IJwtTokenService
  {
      string UnprotectToken(string protectedText);
  }
  
  public class JwtTokenService : IJwtTokenService
  {
      private readonly IDataSerializer&lt;AuthenticationTicket&gt; _ticketSerializer;
      private readonly IDataProtector _dataProtector;
  
      public JwtTokenService(IDataSerializer&lt;AuthenticationTicket&gt; serializer, IDataProtector protector)
      {
          _ticketSerializer = serializer;
          _dataProtector = protector;
      }
  
      public string UnprotectToken(string protectedText)
      {
          SecurityKey signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("mysupersecret_secretkey!123"));
  
          TokenValidationParameters tokenValidationParameters =
              new TokenValidationParameters
              {
                  ValidateIssuerSigningKey =
                      true,
                  IssuerSigningKey = signingKey,
  
                  ValidateIssuer = true,
                  ValidIssuer = "ExampleIssuer",
  
                  ValidateAudience = true,
                  ValidAudience = "ExampleAudience",
  
                  ValidateLifetime = true,
                  ClockSkew = TimeSpan.Zero,
              };
  
          JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
  
          AuthenticationTicket authTicket =
              _ticketSerializer.Deserialize(_dataProtector.Unprotect(Base64UrlTextEncoder.Decode(protectedText)));
  
          if (authTicket.Properties == null || !authTicket.Properties.Items.Any())
              return null;
  
          if (!authTicket.Properties.Items.TryGetValue("jwt", out string embeddedJwt))
              throw new ArgumentException("No JWT was found in the Authentication Ticket");
  
          handler.ValidateToken(embeddedJwt, tokenValidationParameters, out SecurityToken validToken);
  
          JwtSecurityToken validJwt;
  
          if ((validJwt = validToken as JwtSecurityToken) == null)
              throw new ArgumentException("Invalid JWT");
  
          if (!validJwt.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.Ordinal))
              throw new ArgumentException($"Algorithm must be '{SecurityAlgorithms.HmacSha256}'");
  
  
          return embeddedJwt;
      }
  }
  
  public class JwtTokenValidator : ISecureDataFormat&lt;AuthenticationTicket&gt;
  {
      private readonly string _algorithm;
      private readonly TokenValidationParameters _validationParameters;
      private readonly IDataSerializer&lt;AuthenticationTicket&gt; _ticketSerializer;
      private readonly IDataProtector _dataProtector;
  
      public JwtTokenValidator(string algorithm, TokenValidationParameters validationParameters,
          IDataSerializer&lt;AuthenticationTicket&gt; ticketSerializer, IDataProtector dataProtector)
      {
          _algorithm = algorithm;
          _validationParameters = validationParameters;
          _ticketSerializer = ticketSerializer;
          _dataProtector = dataProtector;
      }
  
      public AuthenticationTicket Unprotect(string protectedText) =&gt; Unprotect(protectedText, null);
  
      public AuthenticationTicket Unprotect(string protectedText, string purpose)
      {
          JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
          AuthenticationTicket authTicket;
  
          try
          {
              authTicket =
                  _ticketSerializer.Deserialize(_dataProtector.Unprotect(Base64UrlTextEncoder.Decode(protectedText)));
  
              if (authTicket.Properties == null || !authTicket.Properties.Items.Any())
                  return null;
  
              if (!authTicket.Properties.Items.TryGetValue("jwt", out string embeddedJwt))
                  throw new ArgumentException("No JWT was found in the Authentication Ticket");
  
              IdentityModelEventSource.ShowPII = true;
              handler.ValidateToken(embeddedJwt, this._validationParameters, out SecurityToken validToken);
  
              JwtSecurityToken validJwt;
  
              if ((validJwt = validToken as JwtSecurityToken) == null)
                  throw new ArgumentException("Invalid JWT");
  
              if (!validJwt.Header.Alg.Equals(_algorithm, StringComparison.Ordinal))
                  throw new ArgumentException($"Algorithm must be '{_algorithm}'");
          }
          catch (Exception exception)
          {
              return null;
          }
  
          return authTicket;
      }
  
      public string Protect(AuthenticationTicket data) =&gt; Protect(data, null);
  
      public string Protect(AuthenticationTicket data, string purpose)
      {
          byte[] array = _ticketSerializer.Serialize(data);
          IDataProtector dataProtector = _dataProtector;
  
          if (!string.IsNullOrEmpty(purpose))
              dataProtector = dataProtector.CreateProtector(purpose);
  
          return Base64UrlTextEncoder.Encode(dataProtector.Protect(array));
      }
  }
  </code></pre>
  
  <p>Any idea what's wrong? I also could share a GitHub repo.</p>
  
  <p><strong>update:</strong></p>
  
  <p>Both, API and website are running on the same server. Now I added into Startup for API:</p>
  
  <pre><code>services
              .AddDataProtection(options =&gt; options.ApplicationDiscriminator = "SebastianKlehn")
              .PersistKeysToFileSystem(new DirectoryInfo(Configuration.GetSection("AppSettings:KeyStore").Value))
              .SetApplicationName("KlehnPhotography.API");
  </code></pre>
  
  <p>and in Startup for Website:</p>
  
  <pre><code>services                
              .AddDataProtection(options =&gt; options.ApplicationDiscriminator = "SebastianKlehn")
              .PersistKeysToFileSystem(new DirectoryInfo(Configuration.GetSection("AppSettings:KeyStore").Value))
              .SetApplicationName("KlehnPhotography.Site");
  </code></pre>
  
  <p>When I didn't set <code>options.ApplicationDiscriminator</code>, no file was generated</p>
  
  <p>When I start the application, I get a file in the directory of KeyStore. Each time I remove this file and I reload the website, a new file is generated with a different GUID. After removing cookies and login I get this exception:</p>
  
  <blockquote>
    <p>CryptographicException: The key {45030352-111b-460c-8419-94b48817f170} was not found in the key ring.</p>
  </blockquote>
  
  <p>So I guess, the website always searches for this (and always for the same) GUID: <code>45030352-111b-460c-8419-94b48817f170</code> it never changes even after iisreset - that file is not existent in the KeyStore-folder.</p>
  
  <p>In the log file for API there's:</p>
  
  <pre><code>info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[58]
        Creating key {59dc92b2-1932-43d7-bb99-99c2a16e12fb} with creation date 2019-12-25 17:33:17Z, activation date 2019-12-25 17:33:17Z, and expiration date 2020-03-24 17:33:17Z.
  warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
        No XML encryptor configured. Key {59dc92b2-1932-43d7-bb99-99c2a16e12fb} may be persisted to storage in unencrypted form.
  info: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[39]
        Writing data to file 'E:\wwwroot\Keys\key-59dc92b2-1932-43d7-bb99-99c2a16e12fb.xml'.
  </code></pre>
  
  <p>In the log file for website there is nothing about DataProtection or error or anything. Just the normal things like <code>Now listening on...</code> and <code>Application started</code></p>