-
Notifications
You must be signed in to change notification settings - Fork 28
Make JwtBearerService public with virtual methods for extensibility #191
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,27 +6,35 @@ | |
|
|
||
| namespace SimpleAuthentication.JwtBearer; | ||
|
|
||
| internal class JwtBearerService(IOptions<JwtBearerSettings> jwtBearerSettingsOptions) : IJwtBearerService | ||
| /// <summary> | ||
| /// Default implementation of <see cref="IJwtBearerService"/> that provides JWT Bearer token generation and validation. | ||
| /// </summary> | ||
| /// <param name="jwtBearerSettingsOptions">The JWT Bearer settings.</param> | ||
| public class JwtBearerService(IOptions<JwtBearerSettings> jwtBearerSettingsOptions) : IJwtBearerService | ||
| { | ||
| private readonly JwtBearerSettings jwtBearerSettings = jwtBearerSettingsOptions.Value; | ||
| /// <summary> | ||
| /// Gets the JWT Bearer settings used by this service. | ||
| /// </summary> | ||
| protected JwtBearerSettings JwtBearerSettings { get; } = jwtBearerSettingsOptions.Value; | ||
|
|
||
| public Task<string> CreateTokenAsync(string userName, IList<Claim>? claims = null, string? issuer = null, string? audience = null, DateTime? absoluteExpiration = null) | ||
| /// <inheritdoc /> | ||
| public virtual Task<string> CreateTokenAsync(string userName, IList<Claim>? 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) | ||
|
Comment on lines
+20
to
+37
|
||
| }; | ||
|
|
||
| var tokenHandler = new JsonWebTokenHandler(); | ||
|
|
@@ -35,7 +43,8 @@ public Task<string> CreateTokenAsync(string userName, IList<Claim>? claims = nul | |
| return Task.FromResult(token); | ||
| } | ||
|
|
||
| public async Task<ClaimsPrincipal> ValidateTokenAsync(string token, bool validateLifetime = true) | ||
| /// <inheritdoc /> | ||
| public virtual async Task<ClaimsPrincipal> ValidateTokenAsync(string token, bool validateLifetime = true) | ||
| { | ||
| var tokenHandler = new JsonWebTokenHandler(); | ||
|
|
||
|
|
@@ -46,23 +55,23 @@ public async Task<ClaimsPrincipal> 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<ClaimsPrincipal> ValidateTokenAsync(string token, bool validat | |
| return principal; | ||
| } | ||
|
|
||
| public async Task<string> RefreshTokenAsync(string token, bool validateLifetime, DateTime? absoluteExpiration = null) | ||
| /// <inheritdoc /> | ||
| public virtual async Task<string> 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; | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this type is now part of the public API surface, the primary-constructor parameter should be validated. As written, passing a null IOptions will result in a NullReferenceException when initializing JwtBearerSettings; prefer throwing ArgumentNullException (and optionally validate jwtBearerSettingsOptions.Value as well) to fail fast with a clearer error.