Code Monkey home page Code Monkey logo

security.jwt's Introduction

JWT Key Management for .NET - Generate and auto rotate Cryptographic Keys for your Jwt (jws) / Jwe

One of the biggest problem at Key Management is: How to distribute keys in a security way. HMAC relies on sharing the key between many projects. To accomplish it NetDevPack.Security.Jwt use Public Key Cryptosystem to generate your keys. So you can share you public key at https://<your_api_adrress>/jwks!

read before

Are you creating Jwt like this?

read before

Let me tell you: You have a problem.



NugetcoverageNetDevPack - MASTER Publish

The goal of this project is to help your application security by Managing your JWT.

  • Auto create RSA or ECDsa keys
  • Support for JWE
  • Support public jwks_uri endpoint with your public key in JWKS format
  • Extensions for your client API's to consume the JWKS endpoint. See more at NetDevack.Security.JwtExtensions
  • Auto rotate key every 90 days (Following NIST Best current practices for Public Key Rotation)
  • Remove old private keys after key rotation (NIST Recommendations)
  • Use recommended settings for RSA & ECDSA (RFC 7518 Recommendations)
  • Uses random number generator to generate keys for JWE with AES CBC (dotnet does not support RSA-OAEP with Aes128GCM)
  • By default Save keys in same room of ASP.NET DataProtection (The same place where ASP.NET save the keys to to cryptograph MVC cookies)

It generates Keys way better with RSA and ECDsa algorithms. Which is most recommended by RFC 7518.

Token Validation

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = "https://www.devstore.academy",
        ValidAudience = "NetDevPack.Security.Jwt.AspNet"
    };
});
builder.Services.AddAuthorization();
builder.Services.AddJwksManager().UseJwtValidation();

Generating Tokens:

public AuthController(IJwtService jwtService)
{
    _jwtService = jwtService;
}

private string GenerateToken(User user)
{
    var key = _jwtService.GetCurrentSigningCredentials(); // (ECDsa or RSA) auto generated key
 
    var handler = new JsonWebTokenHandler();
    var now = DateTime.Now;
    var descriptor = new SecurityTokenDescriptor
    {
        Issuer = "https://www.devstore.academy", // <- Your website
        Audience = "NetDevPack.Security.Jwt.AspNet",
        IssuedAt = now,
        NotBefore = now,
        Expires = now.AddMinutes(60),
        Subject = new ClaimsIdentity(FakeClaims.GenerateClaim().Generate(5)),
        SigningCredentials = await service.GetCurrentSigningCredentials()
    };
    return tokenHandler.WriteToken(token);
}

Table of Contents


🛡ī¸ What is

The JSON Web Key Set (JWKS) is a collection of public keys used for verifying JSON Web Tokens (JWTs) issued by an authorization server. This component's primary objective is to provide a centralized storage and key rotation for your JWKs while adhering to best practices in JWK generation. It features a plugin for IdentityServer4, enabling automatic rotation of the jwks_uri every 90 days and seamless management of your jwks_uri.

If your API or OAuth 2.0 is deployed under a Load Balancer in Kubernetes or Docker Swarm, this component is essential. Its functionality is similar to the DataProtection Key in ASP.NET Core.

This component generates, stores, and manages your JWKs while maintaining a centralized storage accessible across instances. By default, a new key is generated every three months.

You can expose your JWKs through a JWKS endpoint and share them with your APIs.

ℹī¸ Installing

To install NetDevPack.Security.Jwt in your API, use the following command in the NuGet Package Manager console:

Install-Package NetDevPack.Security.Jwt

Alternatively, you can use the .NET Core command line interface:

dotnet add package NetDevPack.Security.Jwt

Next, modify the Configure method in your Startup.cs or program.cs file:

builder.Services.AddJwksManager().UseJwtValidation();

❤ī¸ Token Generation

In most cases, when we say JWT, we're actually referring to JWS.

public AuthController(IJwtService jwtService)
{
    _jwtService = jwtService;
}

private string GenerateToken(User user)
{
    var tokenHandler = new JwtSecurityTokenHandler();
    var currentIssuer = $"{ControllerContext.HttpContext.Request.Scheme}://{ControllerContext.HttpContext.Request.Host}";

    var key = _jwtService.GetCurrentSigningCredentials(); // (ECDsa or RSA) auto generated key
    var token = tokenHandler.CreateToken(new SecurityTokenDescriptor
    {
        Issuer = currentIssuer,
        Subject = identityClaims,
        Expires = DateTime.UtcNow.AddHours(1),
        SigningCredentials = key
    });
    return tokenHandler.WriteToken(token);
}

✔ī¸ Token Validation (JWS)

Utilize the same service to obtain the current key and validate the token..

public AuthController(IJwtService jwtService)
{
    _jwtService = jwtService;
}

private string ValidateToken(string jwt)
{
    var handler = new JsonWebTokenHandler();
    var currentIssuer = $"{ControllerContext.HttpContext.Request.Scheme}://{ControllerContext.HttpContext.Request.Host}";

    var result = handler.ValidateToken(jwt,
        new TokenValidationParameters
        {
            ValidIssuer = currentIssuer,
            SigningCredentials = _jwtService.GetCurrentSigningCredentials()
        });
    
    result.IsValid.Should().BeTrue();
}

⛅ Multiple API's - Use Jwks

A major challenge in key management is securely distributing keys. HMAC depends on sharing a key among multiple projects. To address this, NetDevPack.Security.Jwt employs a Public Key Cryptosystem for generating keys. As a result, you can share your public key at https://<your_api_address>/jwks!

Peace of cake 🎂

Identity API (Who emits the token)

Install NetDevPack.Security.Jwt.AspNetCore in the API that issues JWT Tokens. Modify your Startup.cs:

public void Configure(IApplicationBuilder app)
{
    app.UseJwksDiscovery().UseJwtValidation();
}

Generating the token:

 private string EncodeToken(ClaimsIdentity identityClaims)
{
    var tokenHandler = new JwtSecurityTokenHandler();
    var currentIssuer = $"{ControllerContext.HttpContext.Request.Scheme}://{ControllerContext.HttpContext.Request.Host}";

    var key = _jwksService.GetCurrentSigningCredentials();
    var token = tokenHandler.CreateToken(new SecurityTokenDescriptor
    {
        Issuer = currentIssuer,
        Subject = identityClaims,
        Expires = DateTime.UtcNow.AddHours(1),
        SigningCredentials = key
    });
    return tokenHandler.WriteToken(token);
}

Client API

In your Client API, where JWT validation is required, install NetDevPack.Security.JwtExtensions. Next, update your Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(x =>
    {
        x.RequireHttpsMetadata = true;
        x.SaveToken = true; // keep the public key at Cache for 10 min.
        x.IncludeErrorDetails = true; // <- great for debugging
        x.SetJwksOptions(new JwkOptions("https://localhost:5001/jwks"));
    });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // ...
    app.UseAuthentication();
    app.UseAuthorization();
    // ...
}

At your Controller:

[Authorize]
public class IdentityController : ControllerBase
{
    public IActionResult Get()
    {
        return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
    }
}

Done 👌!

💾 Storage

By default, NetDevPack.Security.Jwt stores keys in the same location where ASP.NET Core stores its Cryptographic Key Material. It utilizes the IXmlRepository.

Any changes made to DataProtection will apply to this as well.

You can override the default behavior by adding another provider and customizing it according to your needs.

Database

The NetDevPack.Security.Jwt package offers a method for storing your keys in a database using EntityFramework Core.

Install via NuGet Package Manager:

    Install-Package NetDevPack.Security.Jwt.Store.EntityFrameworkCore

Or through the .NET Core command line interface:

    dotnet add package NetDevPack.Security.Jwt.Store.EntityFrameworkCore

Add ISecurityKeyContext to your DbContext:

class MyKeysContext : DbContext, ISecurityKeyContext
{
    public MyKeysContext(DbContextOptions<MyKeysContext> options) : base(options) { }

    // This maps to the table that stores keys.
    public DbSet<SecurityKeyWithPrivate> DataProtectionKeys { get; set; }
}

Then change your confinguration at Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddJwksManager().PersistKeysToDatabaseStore<MyKeysContext>();
}

Done!

File system

The NetDevPack.Security.Jwt package provides a mechanism for storing yor Keys to filesystem.

Install

    Install-Package NetDevPack.Security.Jwt.Store.FileSystem

Or via the .NET Core command line interface:

    dotnet add package NetDevPack.Security.Jwt.Store.FileSystem

Now change your startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddJwksManager().PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp-keys\"));
}

Samples

You can find several examples here

Changing Algorithm

It's possible to modify the default algorithm during the configuration process.

build.Services.AddJwksManager(o =>
{
    o.Jws = Algorithm.Create(DigitalSignaturesAlgorithm.RsaSsaPssSha256);
    o.Jwe = Algorithm.Create(EncryptionAlgorithmKey.RsaOAEP).WithContentEncryption(EncryptionAlgorithmContent.Aes128CbcHmacSha256);
});

By default, it uses recommended algorithms according to RFC7518

build.Services.AddJwksManager(o =>
{
    o.Jws { get; set; } = Algorithm.Create(AlgorithmType.RSA, JwtType.Jws);
    o.Jwe { get; set; } = Algorithm.Create(AlgorithmType.RSA, JwtType.Jwe);
}

The Algorithm object offers a variety of options to choose from.

Jws

Algorithms:

Shortname Name
HS256 Hmac Sha256
HS384 Hmac Sha384
HS512 Hmac Sha512
RS256 Rsa Sha256
RS384 Rsa Sha384
RS512 Rsa Sha512
PS256 Rsa SsaPss Sha256
PS384 Rsa SsaPss Sha384
PS512 Rsa SsaPss Sha512
ES256 Ecdsa Sha256
ES384 Ecdsa Sha384
ES512 Ecdsa Sha512

Jwe

Algorithms options:

Shortname Key Management Algorithm
RSA1_5 RSA1_5
RsaOAEP RSAES OAEP using
A128KW A128KW
A256KW A256KW

Encryption options

Shortname Content Encryption Algorithm
Aes128CbcHmacSha256 A128CBC-HS256
Aes192CbcHmacSha384 A192CBC-HS384
Aes256CbcHmacSha512 A256CBC-HS512

IdentityServer4 - Auto jwks_uri Management

NetDevPack.Security.Jwt provides IdentityServer4 key material. It auto generates and rotate key.

First install

    Install-Package NetDevPack.Security.Jwt.IdentityServer4

Or via the .NET Core command line interface:

    dotnet add package NetDevPack.Security.Jwt.IdentityServer4

Go to Startup.cs

    public void ConfigureServices(IServiceCollection services)
    {
        var builder = services.AddIdentityServer()
            .AddInMemoryIdentityResources(Config.GetIdentityResources())
            .AddInMemoryApiResources(Config.GetApis())
            .AddInMemoryClients(Config.GetClients());

        services.AddJwksManager().IdentityServer4AutoJwksManager();
    }

If you wanna use Database, follow instructions to DatabaseStore instead.

Why

When developing applications and APIs using OAuth 2.0 or simply signing a JWT key, various algorithms are supported. Among these algorithms, some are considered best practices and superior to others, such as the Elliptic Curve with PS256 algorithm. Certain Auth servers operate with deterministic algorithms, while others use probabilistic ones. Some servers, like Auth0, do not support multiple JWKs, but IdentityServer4 supports as many as you configure. This component is designed to abstract this layer and offer your application the current best practices for JWK management.

Load Balance scenarios

When working with containers in Kubernetes or Docker Swarm, scaling your applications can lead to certain issues, such as needing to store DataProtection keys in a centralized location. While it is not recommended to bypass this situation, using symmetric keys is one possible solution. Similar to DataProtection, this component provides a centralized store for your JWKS.

Best practices

Many developers are unsure about which algorithm to use for signing their JWTs. By default, this component uses Elliptic Curve with ECDSA, utilizing P-256 and SHA-256 to help build more secure APIs and environments. It simplifies JWKS management by providing a better understanding of best practices and ensuring the use of secure algorithms.


License

NetDevPack.Security.Jwt is Open Source software and is released under the MIT license. This license allow the use of NetDevPack.Security.Jwt in free and commercial applications and libraries without restrictions.

security.jwt's People

Contributors

50c avatar brunobritodev avatar eduardopires avatar ehabhussein avatar eliezeralmeida avatar kirill-gerasimenko-da avatar maacpiash avatar ralmsdeveloper avatar semantic-release-bot avatar thompson-tomo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

security.jwt's Issues

PS256 is not the recommended one by rfc7518

By default my generated token has alg header "PS256".

According to
https://datatracker.ietf.org/doc/html/rfc7518

ES256: Recommended+
RS256: Recommended

Looks like it should be ES256 by default?

P.S. I might not fully understand what "Recommended" is. Looks like it means "recommended for implementations". Nevertheless default algorithm should not be "optional for implementation" I think.
Generally I'm unable to understand from the RFC which are more secure/less secure.

Question: does EF core store use caching?

Hi!

I've started using the project and doing some experiments under the load, and the access time for the current keys to generate the token caught my attention and I've started digging. So I noticed that here

return await _context.SecurityKeys.OrderByDescending(d => d.CreationDate).AsNoTrackingWithIdentityResolution().FirstOrDefaultAsync();
it does call the database, despite the cache hit. Is this something expected? I'd think that it just has to return credentials found in the memory cache at this point. To compare - I've checked with file system store implementation - and it works as I would expect, returning credentials found in the memory cache.

Please advise.

Thanks for the great library!

Question: Adding support for EncryptingCredentials

When defining the SecurityTokenDescriptor, you can not only set the SigningCredentials, but the EncryptingCredentials as well.

Is there a way to use the same key to set the EncryptingCredentials as well. I've been trying the following, but I've been getting an exception, and I'm not sure how to solve it:

private string EncodeToken(ClaimsIdentity identityClaims)
{
	var tokenHandler = new JwtSecurityTokenHandler();
	var currentIssuer = $"{ControllerContext.HttpContext.Request.Scheme}://{ControllerContext.HttpContext.Request.Host}";
	var key = _jwksService.GetCurrent();

	var encryptingCredentials = new EncryptingCredentials(key.Key, SecurityAlgorithms.RsaOAEP, SecurityAlgorithms.Aes128CbcHmacSha256);
	var token = tokenHandler.CreateToken(new SecurityTokenDescriptor
	{
		Issuer = currentIssuer,
		Subject = identityClaims,
		Expires = DateTime.UtcNow.AddHours(1),
		SigningCredentials = key,
		EncryptingCredentials = encryptingCredentials
	});

	return tokenHandler.WriteToken(token);
}

Exception is:

Microsoft.IdentityModel.Tokens.SecurityTokenEncryptionFailedException: IDX10615: Encryption failed. No support for: Algorithm: '', SecurityKey: 'Microsoft.IdentityModel.Tokens.JsonWebKey, Use: 'sig',  Kid: '0EfgPGGNZBsnn69wrnwLzg', Kty: 'EC', InternalId: '2cIn0xM5H76UBp4u-Bx2MRU-S2YAS0XsHqe67NE4Cbk'.'.
   at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.CreateEncryptedToken(JwtSecurityToken innerJwt, EncryptingCredentials encryptingCredentials)
   at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.CreateJwtSecurityTokenPrivate(String issuer, String audience, ClaimsIdentity subject, Nullable`1 notBefore, Nullable`1 expires, Nullable`1 issuedAt, SigningCredentials signingCredentials, EncryptingCredentials encryptingCredentials)
   at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.CreateToken(SecurityTokenDescriptor tokenDescriptor)

What is the alleged security problem addressed in the README

The somewhat clickbait header in the README appears to claim that symmetric JWS with HMAC/SHA-256 possesses a security problem. I am curious what the authors claim to be the problem here exactly as it is not elaborated in the text whatsoever and the RFC yield said signature method a svalid.

.NET 8.0 incompatibility with HS256

I use IJwtService with the following code to generate access token:

        var credentials = await jwtService.GetCurrentSigningCredentials();
        var tokenDescriptor = _jwtSecurityTokenHandler.CreateJwtSecurityToken(_issuer, _issuer,
            new ClaimsIdentity(claims), expires: DateTime.UtcNow.AddMinutes(60), signingCredentials: credentials);

I get here:

System.NotSupportedException: 'IDX10621: 'Microsoft.IdentityModel.Tokens.SymmetricSignatureProvider' supports: 'Microsoft.IdentityModel.Tokens.SecurityKey' of types: 'Microsoft.IdentityModel.Tokens.AsymmetricSecurityKey' or 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey'. SecurityKey received was of type 'Microsoft.IdentityModel.Tokens.JsonWebKey'.'

Might be related to

Workaround is not to use HS256

Remove STJ from net 6+

Describe the feature.

I want my application's and libraries to be as small as possible and the minimum amount of dependencies.

Is your feature related to a problem?

Additional dependencies are being added to my projects.

Describe the requested feature

When the framework is able to natively provide a dependency ie System.Text.Json that should be utilised rather than an explicit dependency. This will be the case for the net 5+ TFM.

Describe alternatives you've considered

Accept the additional dependencies

Additional Context

No response

AggregateException when building

Hey, been trying to migrate from NetDevPack.Identity jwt to this one but I am getting the following error when building, any help would be appreciated.

System.AggregateException: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: NetDevPack.Security.Jwt.Core.Interfaces.IJwtService Lifetime: Scoped ImplementationType: NetDevPack.Security.Jwt.Core.Jwt.JwtService': Unable to resolve service for type 'Microsoft.Extensions.Caching.Memory.IMemoryCache' while attempting to activate 'NetDevPack.Security.Jwt.Core.DefaultStore.DataProtectionStore'.) (Error while validating the service descriptor 'ServiceType: NetDevPack.Security.Jwt.Core.Interfaces.IJsonWebKeyStore Lifetime: Scoped ImplementationType: NetDevPack.Security.Jwt.Core.DefaultStore.DataProtectionStore': Unable to resolve service for type 'Microsoft.Extensions.Caching.Memory.IMemoryCache' while attempting to activate 'NetDevPack.Security.Jwt.Core.DefaultStore.DataProtectionStore'.)
 ---> System.InvalidOperationException: Error while validating the service descriptor 'ServiceType: NetDevPack.Security.Jwt.Core.Interfaces.IJwtService Lifetime: Scoped ImplementationType: NetDevPack.Security.Jwt.Core.Jwt.JwtService': Unable to resolve service for type 'Microsoft.Extensions.Caching.Memory.IMemoryCache' while attempting to activate 'NetDevPack.Security.Jwt.Core.DefaultStore.DataProtectionStore'.
 ---> System.InvalidOperationException: Unable to resolve service for type 'Microsoft.Extensions.Caching.Memory.IMemoryCache' while attempting to activate 'NetDevPack.Security.Jwt.Core.DefaultStore.DataProtectionStore'.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(ServiceDescriptor serviceDescriptor, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.ValidateService(ServiceDescriptor descriptor)
   --- End of inner exception stack trace ---
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.ValidateService(ServiceDescriptor descriptor)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(ICollection`1 serviceDescriptors, ServiceProviderOptions options)
   --- End of inner exception stack trace ---
   at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(ICollection`1 serviceDescriptors, ServiceProviderOptions options)
   at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services, ServiceProviderOptions options)
   at Microsoft.Extensions.DependencyInjection.DefaultServiceProviderFactory.CreateServiceProvider(IServiceCollection containerBuilder)
   at Microsoft.Extensions.Hosting.Internal.ServiceFactoryAdapter`1.CreateServiceProvider(Object containerBuilder)
   at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
   at Microsoft.Extensions.Hosting.HostBuilder.Build()
   at Microsoft.AspNetCore.Builder.WebApplicationBuilder.Build()
   at Program.<Main>$(String[] args) in D:\PROJECT\Program.cs:line 43
 ---> (Inner Exception #1) System.InvalidOperationException: Error while validating the service descriptor 'ServiceType: NetDevPack.Security.Jwt.Core.Interfaces.IJsonWebKeyStore Lifetime: Scoped ImplementationType: NetDevPack.Security.Jwt.Core.DefaultStore.DataProtectionStore': Unable to resolve service for type 'Microsoft.Extensions.Caching.Memory.IMemoryCache' while attempting to activate 'NetDevPack.Security.Jwt.Core.DefaultStore.DataProtectionStore'.
 ---> System.InvalidOperationException: Unable to resolve service for type 'Microsoft.Extensions.Caching.Memory.IMemoryCache' while attempting to activate 'NetDevPack.Security.Jwt.Core.DefaultStore.DataProtectionStore'.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(ServiceDescriptor serviceDescriptor, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.ValidateService(ServiceDescriptor descriptor)
   --- End of inner exception stack trace ---
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.ValidateService(ServiceDescriptor descriptor)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(ICollection`1 serviceDescriptors, ServiceProviderOptions options)<---

Add data protection to DatabaseJsonWebKeyStore and FileSystemStore

Recently switch from DataProtectionStore to DatabaseJsonWebKeyStore and noticed that no DataProtection is present.
It looks to me that mentioned stores are generally less secure than default one.

Note that for example MsalDistributedTokenCacheAdapterOptions has an option to Encrypt (default false):

        services.Configure<MsalDistributedTokenCacheAdapterOptions>(options =>
        {
            // Just for extra security here
            options.Encrypt = true;
        });

I added protection to DatabaseJsonWebKeyStore like this:

            keyModel.Property(key => key.Parameters).HasColumnName("parameters").HasConversion(
                val => Protect(val), dbVal => Unprotect(dbVal)
            );

With:

    string Protect(string val)
    {
        return dataProtector.Protect(val);
    }

    string Unprotect(string dbVal)
    {
        try
        {
            return dataProtector.Unprotect(dbVal);
        }
        catch
        {
            // Something bad but also maybe unprotected payload
            return dbVal;
        }
    }

But I think would be nice to have it in the stores out of the box.

The other option will be to add protection at a higher level for KeyMaterial but that won't work good for some scenarios. For example I'd like to store a public key separately so I can access it from other services but keep private key only to the specific service.

Cache entry must specify a value for Size when SizeLimit is set

"NetDevPack.Security.Jwt.AspNetCore" Version="5.0.9"
"NetDevPack.Security.Jwt.Store.EntityFrameworkCore" Version="5.0.9"

Ao pedir o SigningCredentials atual a aplicaçÃŖo joga a exceçÃŖo abaixo.
Esse erro somente acontece se jÃĄ existe um token no banco de dados.

Cache entry must specify a value for Size when SizeLimit is set.

at Microsoft.Extensions.Caching.Memory.MemoryCache.SetEntry(CacheEntry entry)
   at Microsoft.Extensions.Caching.Memory.CacheEntry.Dispose()
   at Microsoft.Extensions.Caching.Memory.CacheExtensions.Set[TItem](IMemoryCache cache, Object key, TItem value, MemoryCacheEntryOptions options)
   at NetDevPack.Security.JwtSigningCredentials.Store.EntityFrameworkCore.DatabaseJsonWebKeyStore`1.GetCurrentKey(JsonWebKeyType jwkType)
   at NetDevPack.Security.JwtSigningCredentials.Store.EntityFrameworkCore.DatabaseJsonWebKeyStore`1.NeedsUpdate(JsonWebKeyType jsonWebKeyType)
   at NetDevPack.Security.JwtSigningCredentials.Jwks.JwksService.GetCurrentSigningCredentials(JwksOptions options)
   at xxx.Identity.Application.Token.JwtToken.GenerateToken(User user, UserTokenType userTokenType) in C:\Projects\xxx\xxx.Identity.Application\Token\JwtToken.cs:line 132

TambÊm ocorre o erro se coloco a requisiçÃŖo 1 abaixo da outra.

image

image

O erro descrito acima acontece no momento de salvar o cache em memÃŗria.

image

Ajustando o Size conforme imagem abaixo o erro Ê resolvido.

image

NÃŖo estou utilizando o IdentityServer4!

How can I migrate an old ES256 key file to the database?

I have a current key stored in the file format with the old alg ES256. Now the default is RSA. I'm trying to migrate from File store to the Database using EF but I wouldn't like my users to lose their current jwt. Is there a way to migrate the old key file to the database? The new keys would be generated using the alg RSA.

Nuget package does not appear to reflect the current src

Example

https://github.com/brunohbrito/Jwks.Manager/blob/master/src/Jwks.Manager/SecurityKeyWithPrivate.cs
stamped d13b29b on Feb 26

defines

public JsonWebKey GetSecurityKey() { return JsonConvert.DeserializeObject<JsonWebKey>(Parameters); }

but nuget package 3.1.1, dated Monday, February 3, 2020 (2/3/2020)

defines

public SecurityKey GetSecurityKey();

Source code indicates Jwks.Manager.Jwks.InMemoryStore is a public class, but it does not appear in the nuget package.

Pulling the latest code, building and checking ildasm confirms.

Clarify key rotation

I'm checking a bit the code and don't understand one thing here.

Every 90 days a new key will be created and old key will be discarded.
It means immediate invalidation of all of the tokens.

Therefore all of the clients need to refresh tokens? Even if they got them like 1 minute ago?
Would it make more sense to still keep the old keys but validate the signature only if it was issued the moment the key was active?
I think this is how pretty much DataProtection key rotation works in .NET Core.

P.S. I'm also using this key for refresh token. It means that all refresh tokens will be also immediately invalidated.

.NET 8.0 incompatibility with SecurityTokenValidators

SecurityTokenValidators are obsolete and needs to be replaced with TokenHandlers

Noticed it because I need to use JwtServiceValidationHandler separately like this:
options.SecurityTokenValidators.Clear();
options.SecurityTokenValidators.Add(new JwtServiceValidationHandler(serviceProvider));

Added this for now to remove when addressed SecurityTokenValidators:
options.UseSecurityTokenValidators = true;

.NET make a warning here:
warning CS0618: 'JwtBearerOptions.SecurityTokenValidators' is obsolete: 'SecurityTokenValidators is no longer used by default. Use TokenHandlers instead. To continue using SecurityTokenValidators, set UseSecurityTokenValidators to true. See https://aka.ms/aspnetcore8/security-token-changes'

CreationDate problem with use Ngpsql.EntityFrameworkCore.PostgreSQL > 6.0

More details: https://www.roji.org/postgresql-dotnet-timestamp-mapping

Details of problem:

From what I understand, basically the problem happens when you have a date with Kind other than UTC, to get around the problem a flag must be changed globally, but it is not a good practice, the best is to use the concept of "UTC Everywhere".

Checking the source code on line 14 of the KeyMaterial file, the Data value starts with Now, this causes the error because of Kind=Local.

Likely correction:
KeyMaterial line 14: DateTime.UtcNow
KeyMaterial line 37: DateTime.UtcNow

This will make Npgsql not throw excpetion on account of Kind other than UTC and also the dates will be bought in UTC ignoring infra setting.

Imagem do erro em um projeto .NET6 com PostgreSQL:

image

Conclusion

If these changes make sense for the project proposal, I can be making and submitting the pull request.
I used google translate to create the text, in case something was confused in the translation I can send it in pt-br.

Warning and retention clarification

  1. When I launch the app I see the following warning:
    warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[15]
    Unknown element with name 'NetDevPackSecurityJwt' found in keyring, skipping.

Is it anyhow an issue? I understand that NetDevPackSecurity stores my keys in the same folder as ASP.NET but looks like their XML files start with <key> and NetDevPackSecurity uses <NetDevPackSecurityJwt> root.
Is it intentional?

  1. I see in ASP.NET files . Does NetDevPackSecurity also encrypts keys?

  2. I have multiple files from NetDevPackSecurity there (6 actually). Do I need to do something special to cleanup them? Well I changed key type couple of times - maybe that's why. Looks like expiration dates are not yet expired.

I went to that folder and made a key expired. Specifically changed expirationDate. But it still uses it and the file is still there. Are files anyhow deleted from there?

JwtTokenService on startup creates as many credentials as there are concurrent requests

JwtTokenService is a scoped service and it doesn't have any locking mechanisms.

In case a credential needs to be created (for example on first start or credential expire) it will create as many keys as there are concurrent simultaneous requests.
This is an issue because:

  1. Why my system now has 2 or more keys?
  2. If amount of requests is huge you can create a huge amount of keys. In case you create >AlgorithmsToKeep you will have an issue to validate tokens because JwtServiceValidationHandler will not return the keys to you.

To resolve JwtTokenService might need to become a singleton, resolve IJsonWebKeyStore from a scope and apply some locking.
For optimization you can apply double-locking only if the key is subject to be renewed.

For now I made a workaround like this (somewhere in a singleton returning access tokens):

        await _currentCreds.WaitAsync(cancellationToken);
        try
        {
            var jwtService = httpContext.RequestServices.GetRequiredService<IJwtService>();
            credentials = await jwtService.GetCurrentSigningCredentials();
        }
        finally
        {
            _currentCreds.Release();
        }

This is a bit not ideal as it always locks but better than having lots of tokens created at the same time.

BTW GetCurrentSigningCredentials() I think should also accept CancellationToken as DB store can have some time to create creds and user might already cancel his login?

JsonWebKey deserialization depends on global JsonSerializerOptions

Hi,
I'm a happy user of your helpful library, but recently I have encounted a problem. After some updates on my project, the SigningCredentials returned by the library was not accepted anymore for jwt signing and throws misleading error:

System.NotSupportedException: IDX10621: 'Microsoft.IdentityModel.Tokens.SymmetricSignatureProvider' supports: 'Microsoft.IdentityModel.Tokens.SecurityKey' of types: 'Microsoft.IdentityModel.Tokens.AsymmetricSecurityKey' or 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey'. SecurityKey received was of type 'Microsoft.IdentityModel.Tokens.JsonWebKey'.

After further investigation and debugging, I found that returned SecurityKey has just default values for his properties. This was caused by wrong deserialization of JsonWebKey by KeyMaterial.GetSecurityKey that is depending on JsonSerializerOptions

I have the small fix for this that I could send as merge request.

if the .key file has ExpiredAt and expired, it will always generate new .key file

in FileSystemStore.cs
this code always return the first file, so it will generate new file repeatly.

private string GetCurrentFile()
{
    var files = Directory.GetFiles(KeysPath.FullName, $"*current*.key");
    if (files.Any())
        return Path.Combine(KeysPath.FullName, files.First());

    return Path.Combine(KeysPath.FullName, $"{_options.Value.KeyPrefix}current.key");
}

Works locally, but fails in deployed container with IDX10500

The validation keeps throwing "IDX10500: Signature validation failed. No security keys were provided to validate the signature."

This one is really baffling to me. I tried everything I could find, but what seems to be fairly simple is really hard to get working. Although it should be simple, your library is seemingly the only one that implements the whole 'validate against a jwksuri' -case.

Locally this works fine. The problem is that the container (based on 'mcr.microsoft.com/dotnet/aspnet:8.0') deployed will not work.

dotnet minimal api setup:

CryptoProviderFactory.Default.CacheSignatureProviders = false;
IdentityModelEventSource.ShowPII = true;

builder.Services.AddDataProtection();

builder.Services.AddMemoryCache();

builder.Services.AddJwksManager()
	.UseJwtValidation();

builder.Services.AddAuthentication(options =>
{
	options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
	options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
	options.SaveToken = true;

	options.SetJwksOptions(
		new JwkOptions
		{
			Audience = "audience", Issuer = "https://keycloak.myProduct.eu/realms/myProduct",
			JwksUri = "https://keycloak.myProduct.eu/realms/myProduct/protocol/openid-connect/certs",
			KeepFor = TimeSpan.FromHours(1)
		});
	options.TokenValidationParameters = new TokenValidationParameters
	{
		ValidateIssuer = false,
		ValidateAudience = false,
		ValidateLifetime = false,
		ValidateIssuerSigningKey = true,
		ValidIssuer = "https://keycloak.myProduct.eu/realms/myProduct",
		ValidAudience = "account",
		ClockSkew = TimeSpan.FromHours(2)
	};

	options.Events = new JwtBearerEvents
	{
		OnAuthenticationFailed = c => c.Response.WriteAsync(JsonConvert.SerializeObject(c.Exception)),
	};
});
builder.Services.AddAuthorization();

log:

2024-04-17 22:06:41
warn: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[60]
2024-04-17 22:06:41
Storing keys in a directory '/root/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed. For more information go to https://aka.ms/aspnet/dataprotectionwarning
2024-04-17 22:06:41
info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[62]
2024-04-17 22:06:41
User profile is available. Using '/root/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest.
2024-04-17 22:06:41
dbug: Microsoft.AspNetCore.DataProtection.KeyManagement.DefaultKeyResolver[53]
2024-04-17 22:06:41
Repository contains no viable default key. Caller should generate a key with immediate activation.
2024-04-17 22:06:41
dbug: Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingProvider[57]
2024-04-17 22:06:41
Policy resolution states that a new key should be added to the key ring.
2024-04-17 22:06:41
info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[58]
2024-04-17 22:06:41
Creating key {e92cdd27-bdba-48e6-a9f5-3dc207e12223} with creation date 2024-04-17 22:06:41Z, activation date 2024-04-17 22:06:41Z, and expiration date 2024-07-16 22:06:41Z.
2024-04-17 22:06:41
dbug: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[32]
2024-04-17 22:06:41
Descriptor deserializer type for key {e92cdd27-bdba-48e6-a9f5-3dc207e12223} is 'Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'.
2024-04-17 22:06:41
dbug: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[34]
2024-04-17 22:06:41
No key escrow sink found. Not writing key {e92cdd27-bdba-48e6-a9f5-3dc207e12223} to escrow.
2024-04-17 22:06:41
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
2024-04-17 22:06:41
No XML encryptor configured. Key {e92cdd27-bdba-48e6-a9f5-3dc207e12223} may be persisted to storage in unencrypted form.
2024-04-17 22:06:41
info: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[39]
2024-04-17 22:06:41
Writing data to file '/root/.aspnet/DataProtection-Keys/key-e92cdd27-bdba-48e6-a9f5-3dc207e12223.xml'.
2024-04-17 22:06:41
dbug: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[23]
2024-04-17 22:06:41
Key cache expiration token triggered by 'CreateNewKey' operation.
2024-04-17 22:06:41
dbug: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[37]
2024-04-17 22:06:41
Reading data from file '/root/.aspnet/DataProtection-Keys/key-e92cdd27-bdba-48e6-a9f5-3dc207e12223.xml'.
2024-04-17 22:06:41
dbug: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[18]
2024-04-17 22:06:41
Found key {e92cdd27-bdba-48e6-a9f5-3dc207e12223}.
2024-04-17 22:06:41
dbug: Microsoft.AspNetCore.DataProtection.KeyManagement.DefaultKeyResolver[13]
2024-04-17 22:06:41
Considering key {e92cdd27-bdba-48e6-a9f5-3dc207e12223} with expiration date 2024-07-16 22:06:41Z as default key.
2024-04-17 22:06:41
dbug: Microsoft.AspNetCore.DataProtection.TypeForwardingActivator[0]
2024-04-17 22:06:41
Forwarded activator type request from Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60 to Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Culture=neutral, PublicKeyToken=adb9793829ddae60
2024-04-17 22:06:41
dbug: Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ManagedAuthenticatedEncryptorFactory[11]
2024-04-17 22:06:41
Using managed symmetric algorithm 'System.Security.Cryptography.Aes'.
2024-04-17 22:06:41
dbug: Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ManagedAuthenticatedEncryptorFactory[10]
2024-04-17 22:06:41
Using managed keyed hash algorithm 'System.Security.Cryptography.HMACSHA256'.
2024-04-17 22:06:41
dbug: Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingProvider[2]
2024-04-17 22:06:41
Using key {e92cdd27-bdba-48e6-a9f5-3dc207e12223} as the default key.
2024-04-17 22:06:41
dbug: Microsoft.AspNetCore.DataProtection.Internal.DataProtectionHostedService[65]
2024-04-17 22:06:41
Key ring with default key {e92cdd27-bdba-48e6-a9f5-3dc207e12223} was loaded during application startup.
2024-04-17 22:06:41
warn: Microsoft.AspNetCore.Hosting.Diagnostics[15]
2024-04-17 22:06:41
Overriding HTTP_PORTS '8080' and HTTPS_PORTS ''. Binding to values defined by URLS instead 'http://0.0.0.0:5001'.
2024-04-17 22:06:42
dbug: Microsoft.AspNetCore.Hosting.Diagnostics[13]
2024-04-17 22:06:42
Loaded hosting startup assembly myProduct.Platform.Web
2024-04-17 22:07:10
dbug: Microsoft.AspNetCore.Server.Kestrel.Connections[39]
2024-04-17 22:07:10
Connection id "0HN2V5AT6MJH7" accepted.
2024-04-17 22:07:10
dbug: Microsoft.AspNetCore.Server.Kestrel.Connections[1]
2024-04-17 22:07:10
Connection id "0HN2V5AT6MJH7" started.
2024-04-17 22:07:10
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
2024-04-17 22:07:10
Request starting HTTP/1.1 GET http://demo.myProduct.eu/api/BillOfLading - - -
2024-04-17 22:07:10
dbug: Microsoft.AspNetCore.HostFiltering.HostFilteringMiddleware[0]
2024-04-17 22:07:10
Wildcard detected, all requests with hosts will be allowed.
2024-04-17 22:07:10
warn: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[60]
2024-04-17 22:07:10
Storing keys in a directory '/root/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed. For more information go to https://aka.ms/aspnet/dataprotectionwarning
2024-04-17 22:07:10
dbug: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[37]
2024-04-17 22:07:10
Reading data from file '/root/.aspnet/DataProtection-Keys/key-e92cdd27-bdba-48e6-a9f5-3dc207e12223.xml'.
2024-04-17 22:08:10
info: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[1]
2024-04-17 22:08:10
Failed to validate the token.
2024-04-17 22:08:10
Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException: IDX10500: Signature validation failed. No security keys were provided to validate the signature.
2024-04-17 22:08:10
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
2024-04-17 22:08:10
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignatureAndIssuerSecurityKey(String token, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
2024-04-17 22:08:10
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateJWS(String token, TokenValidationParameters validationParameters, BaseConfiguration currentConfiguration, SecurityToken& signatureValidatedToken, ExceptionDispatchInfo& exceptionThrown)
2024-04-17 22:08:10
--- End of stack trace from previous location ---
2024-04-17 22:08:10
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, JwtSecurityToken outerToken, TokenValidationParameters validationParameters, SecurityToken& signatureValidatedToken)
2024-04-17 22:08:10
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)
2024-04-17 22:08:10
at NetDevPack.Security.Jwt.AspNetCore.JwtServiceValidationHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)
2024-04-17 22:08:10
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateTokenAsync(String token, TokenValidationParameters validationParameters)
2024-04-17 22:08:10
info: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[7]
2024-04-17 22:08:10
Bearer was not authenticated. Failure message: IDX10500: Signature validation failed. No security keys were provided to validate the signature.
2024-04-17 22:08:10
dbug: Microsoft.AspNetCore.Routing.Matching.DfaMatcher[1001]
2024-04-17 22:08:10
1 candidate(s) found for the request path '/api/BillOfLading'
2024-04-17 22:08:10
dbug: Microsoft.AspNetCore.Routing.Matching.DfaMatcher[1005]
2024-04-17 22:08:10
Endpoint 'myProduct.Platform.Web.Controllers.BillOfLadingController.GetBillOfLadings (myProduct.Platform.Web)' with route pattern 'api/BillOfLading' is valid for the request path '/api/BillOfLading'
2024-04-17 22:08:10
dbug: Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware[1]
2024-04-17 22:08:10
Request matched endpoint 'myProduct.Platform.Web.Controllers.BillOfLadingController.GetBillOfLadings (myProduct.Platform.Web)'
2024-04-17 22:08:10
dbug: Microsoft.AspNetCore.Authorization.AuthorizationMiddleware[0]
2024-04-17 22:08:10
Policy authentication schemes did not succeed
2024-04-17 22:08:10
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
2024-04-17 22:08:10
Authorization failed. These requirements were not met:
2024-04-17 22:08:10
DenyAnonymousAuthorizationRequirement: Requires an authenticated user.
2024-04-17 22:08:10
fail: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[1]
2024-04-17 22:08:10
An unhandled exception has occurred while executing the request.
2024-04-17 22:08:10
System.InvalidOperationException: StatusCode cannot be set because the response has already started.
2024-04-17 22:08:10
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ThrowResponseAlreadyStartedException(String value)
2024-04-17 22:08:10
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.set_StatusCode(Int32 value)
2024-04-17 22:08:10
at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleChallengeAsync(AuthenticationProperties properties)
2024-04-17 22:08:10
at Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.ChallengeAsync(AuthenticationProperties properties)
2024-04-17 22:08:10
at Microsoft.AspNetCore.Authentication.AuthenticationService.ChallengeAsync(HttpContext context, String scheme, AuthenticationProperties properties)
2024-04-17 22:08:10
at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.<>c__DisplayClass0_0.<<HandleAsync>g__Handle|0>d.MoveNext()
2024-04-17 22:08:10
--- End of stack trace from previous location ---
2024-04-17 22:08:10
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
2024-04-17 22:08:10
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddlewareImpl.<Invoke>g__Awaited|10_0(ExceptionHandlerMiddlewareImpl middleware, HttpContext context, Task task)
2024-04-17 22:08:10
warn: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[2]
2024-04-17 22:08:10
The response has already started, the error handler will not be executed.
2024-04-17 22:08:10
fail: Microsoft.AspNetCore.Server.Kestrel[13]
2024-04-17 22:08:10
Connection id "0HN2V5AT6MJH7", Request id "0HN2V5AT6MJH7:00000001": An unhandled exception was thrown by the application.
2024-04-17 22:08:10
System.InvalidOperationException: StatusCode cannot be set because the response has already started.
2024-04-17 22:08:10
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ThrowResponseAlreadyStartedException(String value)
2024-04-17 22:08:10
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.set_StatusCode(Int32 value)
2024-04-17 22:08:10
at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleChallengeAsync(AuthenticationProperties properties)
2024-04-17 22:08:10
at Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.ChallengeAsync(AuthenticationProperties properties)
2024-04-17 22:08:10
at Microsoft.AspNetCore.Authentication.AuthenticationService.ChallengeAsync(HttpContext context, String scheme, AuthenticationProperties properties)
2024-04-17 22:08:10
at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.<>c__DisplayClass0_0.<<HandleAsync>g__Handle|0>d.MoveNext()
2024-04-17 22:08:10
--- End of stack trace from previous location ---
2024-04-17 22:08:10
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
2024-04-17 22:08:10
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddlewareImpl.<Invoke>g__Awaited|10_0(ExceptionHandlerMiddlewareImpl middleware, HttpContext context, Task task)
2024-04-17 22:08:10
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddlewareImpl.HandleException(HttpContext context, ExceptionDispatchInfo edi)
2024-04-17 22:08:10
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddlewareImpl.<Invoke>g__Awaited|10_0(ExceptionHandlerMiddlewareImpl middleware, HttpContext context, Task task)
2024-04-17 22:08:10
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
2024-04-17 22:08:10
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)
2024-04-17 22:08:10
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
2024-04-17 22:08:10
Request finished HTTP/1.1 GET http://demo.myProduct.eu/api/BillOfLading - 200 - - 60203.1957ms

cat /root/.aspnet/DataProtection-Keys/key-e92cdd27-bdba-48e6-a9f5-3dc207e-12223.xml

<?xml version="1.0" encoding="utf-8"?>
<key id="e92cdd27-bdba-48e6-a9f5-3dc207e12223" version="1">
  <creationDate>2024-04-17T22:06:41.9612458Z</creationDate>
  <activationDate>2024-04-17T22:06:41.9550478Z</activationDate>
  <expirationDate>2024-07-16T22:06:41.9550478Z</expirationDate>
  <descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
    <descriptor>
      <encryption algorithm="AES_256_CBC" />
      <validation algorithm="HMACSHA256" />
      <masterKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection">
        <!-- Warning: the key below is in an unencrypted form. -->
        <value>LIQ26XZ4kAgak0M+PZMJANBeuRu8C6PDvn8KisVgiLw77YNgUcN6UOdWWRWRWWSeGSQQnlgQbPthhSpo7gd9EA==</value>
      </masterKey>
    </descriptor>
  </descriptor>
</key>

Revoke Keys with reason

I have the requirement to accept tokens signed with expired key(resulting in key beeing revoked). This can still be done with your awesome library. But I also wan't accept tokens signed by key expicit revoked by admin user. Currently I can't distinguish both kind of revokation.
I've made some possible extension to enable this by letting me specify the reason for revokation so I can prevent to use some revoked keys I have "blacklisted"
Are you interested in such extension or would you suggest other some solution for my problem.
Thank you

Single app with AddJwtBearer support

In examples, client app is separate from identity app. Is there a way to use the library without .UseJwksDiscovery() and setting the URL in client app since they are the same app.
There is sample for same app but it doesn't use .AddJwtBearer() in startup, it just manually decodes JWT

Thanks

Prefer using IssuerSigningKeyResolver over cleaning TokenHandlers

TokenValidationParameters has IssuerSigningKeyResolver that provides you a kid of the required key immediately.
Adjusting TokenHandlers in JwtServiceValidationHandler has an issue.
It gets a list of keys to check while the amount of keys to retrieve is unknown. In the perfect world you should validate against any key in the database unless it was explicitly revoked.

Other smaller issue is that it's a bit intrusive. Because what if a user added his own validator there and you just deleted it without telling a user about it.

My easy naive approach would be something like this:

        IssuerSigningKeyResolver = (string token, SecurityToken securityToken, string kid, TokenValidationParameters validationParameters) =>
        {
            using var scope = serviceProvider.CreateScope();
            var service = scope.ServiceProvider.GetRequiredService<IJsonWebKeyStore>();
            var key = service.Get(kid).Result;
            return key != null ? [key.GetSecurityKey()] : [];
        },

In this case I'm not sure if GetLastKeys function is needed at all

In this case IJsonWebKeyStore can have a cache on kid directly and doesn't have to cache whole set of keys.

Note in .NET 9 there will be possiblity to make it async

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤ī¸ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.