From 1203a9ac61bf4f9bcea2d8e1875394b6452ed480 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Mar 2026 14:50:21 +0000 Subject: [PATCH 1/3] Initial plan From a6e42b8b91fc14debdc9daafbfe7bd99cf208e03 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Mar 2026 14:54:24 +0000 Subject: [PATCH 2/3] Make JwtBearerService public with virtual methods for extensibility Co-authored-by: marcominerva <3522534+marcominerva@users.noreply.github.com> --- .../JwtBearer/JwtBearerService.cs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/SimpleAuthentication/JwtBearer/JwtBearerService.cs b/src/SimpleAuthentication/JwtBearer/JwtBearerService.cs index ba94857..302c982 100644 --- a/src/SimpleAuthentication/JwtBearer/JwtBearerService.cs +++ b/src/SimpleAuthentication/JwtBearer/JwtBearerService.cs @@ -6,11 +6,19 @@ namespace SimpleAuthentication.JwtBearer; -internal class JwtBearerService(IOptions jwtBearerSettingsOptions) : IJwtBearerService +/// +/// Default implementation of that provides JWT Bearer token generation and validation. +/// +/// The JWT Bearer settings. +public class JwtBearerService(IOptions jwtBearerSettingsOptions) : IJwtBearerService { - private readonly JwtBearerSettings jwtBearerSettings = jwtBearerSettingsOptions.Value; + /// + /// Gets the JWT Bearer settings used by this service. + /// + protected readonly JwtBearerSettings jwtBearerSettings = jwtBearerSettingsOptions.Value; - public Task CreateTokenAsync(string userName, IList? claims = null, string? issuer = null, string? audience = null, DateTime? absoluteExpiration = null) + /// + public virtual Task CreateTokenAsync(string userName, IList? claims = null, string? issuer = null, string? audience = null, DateTime? absoluteExpiration = null) { claims ??= []; claims.Update(jwtBearerSettings.NameClaimType, userName); @@ -35,7 +43,8 @@ public Task CreateTokenAsync(string userName, IList? claims = nul return Task.FromResult(token); } - public async Task ValidateTokenAsync(string token, bool validateLifetime = true) + /// + public virtual async Task ValidateTokenAsync(string token, bool validateLifetime = true) { var tokenHandler = new JsonWebTokenHandler(); @@ -71,7 +80,8 @@ public async Task ValidateTokenAsync(string token, bool validat return principal; } - public async Task RefreshTokenAsync(string token, bool validateLifetime, DateTime? absoluteExpiration = null) + /// + public virtual async Task RefreshTokenAsync(string token, bool validateLifetime, DateTime? absoluteExpiration = null) { var principal = await ValidateTokenAsync(token, validateLifetime); var claims = (principal.Identity as ClaimsIdentity)!.Claims.ToList(); From 11332d39645ce50a5ef35c90a3296feee3cfa1c3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Mar 2026 15:53:58 +0000 Subject: [PATCH 3/3] Change jwtBearerSettings from protected field to protected read-only property Co-authored-by: marcominerva <3522534+marcominerva@users.noreply.github.com> --- .../JwtBearer/JwtBearerService.cs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/SimpleAuthentication/JwtBearer/JwtBearerService.cs b/src/SimpleAuthentication/JwtBearer/JwtBearerService.cs index 302c982..ceeffca 100644 --- a/src/SimpleAuthentication/JwtBearer/JwtBearerService.cs +++ b/src/SimpleAuthentication/JwtBearer/JwtBearerService.cs @@ -15,26 +15,26 @@ public class JwtBearerService(IOptions jwtBearerSettingsOptio /// /// Gets the JWT Bearer settings used by this service. /// - protected readonly JwtBearerSettings jwtBearerSettings = jwtBearerSettingsOptions.Value; + protected JwtBearerSettings JwtBearerSettings { get; } = jwtBearerSettingsOptions.Value; /// public virtual Task CreateTokenAsync(string userName, IList? claims = null, string? issuer = null, string? audience = null, DateTime? absoluteExpiration = null) { claims ??= []; - claims.Update(jwtBearerSettings.NameClaimType, userName); + claims.Update(JwtBearerSettings.NameClaimType, userName); claims.Update(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()); var now = DateTime.UtcNow; var securityTokenDescriptor = new SecurityTokenDescriptor() { - Subject = new ClaimsIdentity(claims, jwtBearerSettings.SchemeName, jwtBearerSettings.NameClaimType, jwtBearerSettings.RoleClaimType), - Issuer = issuer ?? jwtBearerSettings.Issuers?.FirstOrDefault(), - Audience = audience ?? jwtBearerSettings.Audiences?.FirstOrDefault(), + Subject = new ClaimsIdentity(claims, JwtBearerSettings.SchemeName, JwtBearerSettings.NameClaimType, JwtBearerSettings.RoleClaimType), + Issuer = issuer ?? JwtBearerSettings.Issuers?.FirstOrDefault(), + Audience = audience ?? JwtBearerSettings.Audiences?.FirstOrDefault(), IssuedAt = now, - NotBefore = now.Add(-jwtBearerSettings.ClockSkew), - Expires = absoluteExpiration ?? (jwtBearerSettings.ExpirationTime.GetValueOrDefault() > TimeSpan.Zero ? now.Add(jwtBearerSettings.ExpirationTime!.Value) : DateTime.MaxValue), - SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtBearerSettings.SecurityKey)), jwtBearerSettings.Algorithm) + NotBefore = now.Add(-JwtBearerSettings.ClockSkew), + Expires = absoluteExpiration ?? (JwtBearerSettings.ExpirationTime.GetValueOrDefault() > TimeSpan.Zero ? now.Add(JwtBearerSettings.ExpirationTime!.Value) : DateTime.MaxValue), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtBearerSettings.SecurityKey)), JwtBearerSettings.Algorithm) }; var tokenHandler = new JsonWebTokenHandler(); @@ -55,23 +55,23 @@ public virtual async Task ValidateTokenAsync(string token, bool var tokenValidationParameters = new TokenValidationParameters { - AuthenticationType = jwtBearerSettings.SchemeName, - NameClaimType = jwtBearerSettings.NameClaimType, - RoleClaimType = jwtBearerSettings.RoleClaimType, - ValidateIssuer = jwtBearerSettings.Issuers?.Any() ?? false, - ValidIssuers = jwtBearerSettings.Issuers, - ValidateAudience = jwtBearerSettings.Audiences?.Any() ?? false, - ValidAudiences = jwtBearerSettings.Audiences, + AuthenticationType = JwtBearerSettings.SchemeName, + NameClaimType = JwtBearerSettings.NameClaimType, + RoleClaimType = JwtBearerSettings.RoleClaimType, + ValidateIssuer = JwtBearerSettings.Issuers?.Any() ?? false, + ValidIssuers = JwtBearerSettings.Issuers, + ValidateAudience = JwtBearerSettings.Audiences?.Any() ?? false, + ValidAudiences = JwtBearerSettings.Audiences, ValidateIssuerSigningKey = true, - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtBearerSettings.SecurityKey)), + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtBearerSettings.SecurityKey)), RequireExpirationTime = true, ValidateLifetime = validateLifetime, - ClockSkew = jwtBearerSettings.ClockSkew + ClockSkew = JwtBearerSettings.ClockSkew }; var validationResult = await tokenHandler.ValidateTokenAsync(token, tokenValidationParameters); - if (!validationResult.IsValid || validationResult.SecurityToken is not JsonWebToken jsonWebToken || jsonWebToken.Alg != jwtBearerSettings.Algorithm) + if (!validationResult.IsValid || validationResult.SecurityToken is not JsonWebToken jsonWebToken || jsonWebToken.Alg != JwtBearerSettings.Algorithm) { throw new SecurityTokenException("Token is expired or invalid", validationResult.Exception); } @@ -86,7 +86,7 @@ public virtual async Task RefreshTokenAsync(string token, bool validateL var principal = await ValidateTokenAsync(token, validateLifetime); var claims = (principal.Identity as ClaimsIdentity)!.Claims.ToList(); - var userName = claims.First(c => c.Type == jwtBearerSettings.NameClaimType).Value; + var userName = claims.First(c => c.Type == JwtBearerSettings.NameClaimType).Value; var issuer = claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Iss)?.Value; var audience = claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Aud)?.Value;