diff --git a/src/SimpleAuthentication/JwtBearer/JwtBearerService.cs b/src/SimpleAuthentication/JwtBearer/JwtBearerService.cs index ba94857..ceeffca 100644 --- a/src/SimpleAuthentication/JwtBearer/JwtBearerService.cs +++ b/src/SimpleAuthentication/JwtBearer/JwtBearerService.cs @@ -6,27 +6,35 @@ 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 JwtBearerSettings JwtBearerSettings { get; } = 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); + 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(); @@ -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(); @@ -46,23 +55,23 @@ public async Task ValidateTokenAsync(string token, bool validat 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); } @@ -71,12 +80,13 @@ 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(); - 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;