diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index cb36b2cd..0729f08f 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -8,7 +8,7 @@ on: - master env: APP_NAME: Nano.Library - VERSION: 10.0.0-rc2 + VERSION: 10.0.0-rc3 jobs: build-and-deploy: runs-on: windows-latest diff --git a/Directory.Build.props b/Directory.Build.props index 73a56341..02d308da 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -22,12 +22,7 @@ Michael Vivet $(ProjectName) Reusable libraries for building .NET microservice applications - - This package is part of the Nano Library, a set of reusable .NET libraries for building microservice applications. - Nano addresses common non-business concerns such as logging, persistence, messaging, validation, and documentation, - while remaining fully configurable and extensible, so applications can stay focused on business logic. - See https://github.com/Nano-Core/Nano.Library for details. - + This package is part of the Nano Library, a set of reusable .NET libraries for building microservice applications. Nano addresses common non-business concerns such as logging, persistence, messaging, validation, and documentation, while remaining fully configurable and extensible, so applications can stay focused on business logic. See https://github.com/Nano-Core/Nano.Library for details. - .NET 10 support. - Comprehensive rewrite with performance optimizations, improvements, and bug fixes. diff --git a/Nano.App.Api/Mvc/Authentication/AuthJwtRepository.cs b/Nano.App.Api/Mvc/Authentication/AuthJwtRepository.cs index e579a9d1..c31e9935 100644 --- a/Nano.App.Api/Mvc/Authentication/AuthJwtRepository.cs +++ b/Nano.App.Api/Mvc/Authentication/AuthJwtRepository.cs @@ -15,9 +15,42 @@ namespace Nano.App.Api.Mvc.Authentication; /// -public class AuthJwtRepository(JwtAuthenticationOptions options) : IAuthJwtRepository +public class AuthJwtRepository : IAuthJwtRepository { - private readonly JwtAuthenticationOptions options = options ?? throw new ArgumentNullException(nameof(options)); + private readonly JwtAuthenticationOptions options; + private readonly RsaSecurityKey rsaPublicSecurityKey; + private readonly RsaSecurityKey? rsaPrivateSecurityKey; + + /// + /// Initializes a new instance of the class, loading the + /// RSA public key (and private key, if configured) used to validate and sign JWTs. + /// + /// The JWT authentication options, including the RSA public/private keys, issuer, and audience. + /// is . + public AuthJwtRepository(JwtAuthenticationOptions options) + { + ArgumentNullException.ThrowIfNull(options); + + this.options = options ?? throw new ArgumentNullException(nameof(options)); + + this.rsaPublicSecurityKey = this.options.PublicKey + .CreatePublicRsaSecurityKey(); + + this.rsaPrivateSecurityKey = this.options.PrivateKey? + .CreatePrivateRsaSecurityKey(); + } + + /// + /// Releases the unmanaged RSA key handles held by the public and, if present, private instances used for JWT signing and validation. + /// + public void Dispose() + { + this.rsaPublicSecurityKey.Rsa? + .Dispose(); + + this.rsaPrivateSecurityKey?.Rsa? + .Dispose(); + } /// public virtual AccessToken GenerateJwtToken(GenerateJwtToken generateJwtToken) @@ -43,10 +76,7 @@ public virtual AccessToken GenerateJwtToken(GenerateJwtToken generateJwtToken) var notBeforeAt = DateTimeOffset.UtcNow; var expireAt = DateTimeOffset.UtcNow.Add(this.options.Expiration); - var rsaSecurityKey = this.options.PrivateKey? - .CreatePrivateRsaSecurityKey(); - - var signingCredentials = new SigningCredentials(rsaSecurityKey, SecurityAlgorithms.RsaSha512); + var signingCredentials = new SigningCredentials(this.rsaPrivateSecurityKey, SecurityAlgorithms.RsaSha512); var securityToken = new JwtSecurityToken(this.options.Issuer, this.options.Audience, claims, notBeforeAt.DateTime, expireAt.DateTime, signingCredentials); var token = new JwtSecurityTokenHandler() @@ -78,9 +108,6 @@ public virtual RefreshToken GenerateJwtRefreshToken() /// public virtual void ValidateTokenForRefresh(string refreshToken) { - var rsaSecurityKey = this.options.PublicKey - .CreatePublicRsaSecurityKey(); - var validationParameters = new TokenValidationParameters { ValidateIssuer = true, @@ -89,7 +116,7 @@ public virtual void ValidateTokenForRefresh(string refreshToken) ValidateIssuerSigningKey = true, ValidIssuer = this.options.Issuer, ValidAudience = this.options.Audience, - IssuerSigningKey = rsaSecurityKey, + IssuerSigningKey = this.rsaPublicSecurityKey, ClockSkew = TimeSpan.FromMinutes(5) }; diff --git a/Nano.App.Api/Mvc/Authentication/Extensions/StringExtensions.cs b/Nano.App.Api/Mvc/Authentication/Extensions/StringExtensions.cs index 547b6b2a..7d864f71 100644 --- a/Nano.App.Api/Mvc/Authentication/Extensions/StringExtensions.cs +++ b/Nano.App.Api/Mvc/Authentication/Extensions/StringExtensions.cs @@ -12,7 +12,7 @@ internal static RsaSecurityKey CreatePublicRsaSecurityKey(this string key) var base64 = Convert.FromBase64String(key); - using var rsaAlgorithm = RSA.Create(); + var rsaAlgorithm = RSA.Create(); rsaAlgorithm .ImportRSAPublicKey(base64, out _); @@ -26,7 +26,7 @@ internal static RsaSecurityKey CreatePrivateRsaSecurityKey(this string key) var base64 = Convert.FromBase64String(key); - using var rsaAlgorithm = RSA.Create(); + var rsaAlgorithm = RSA.Create(); rsaAlgorithm .ImportRSAPrivateKey(base64, out _);