Make JwtBearerService public with virtual methods for extensibility#191
Make JwtBearerService public with virtual methods for extensibility#191
Conversation
Co-authored-by: marcominerva <3522534+marcominerva@users.noreply.github.com>
…property Co-authored-by: marcominerva <3522534+marcominerva@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Exposes JwtBearerService as a public, extensible implementation of IJwtBearerService so consumers can subclass it to customize JWT creation/validation behavior.
Changes:
- Changed
JwtBearerServicevisibility frominternaltopublic. - Marked
CreateTokenAsync,ValidateTokenAsync, andRefreshTokenAsyncasvirtualfor overriding. - Replaced the private settings field with a
protectedJwtBearerSettingsproperty and added XML docs for the newly-public surface.
You can also share your feedback on Copilot code review. Take the survey.
| /// <summary> | ||
| /// Gets the JWT Bearer settings used by this service. | ||
| /// </summary> | ||
| protected JwtBearerSettings JwtBearerSettings { get; } = jwtBearerSettingsOptions.Value; |
There was a problem hiding this comment.
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.
| protected JwtBearerSettings JwtBearerSettings { get; } = jwtBearerSettingsOptions.Value; | |
| protected JwtBearerSettings JwtBearerSettings { get; } = | |
| jwtBearerSettingsOptions is null | |
| ? throw new ArgumentNullException(nameof(jwtBearerSettingsOptions)) | |
| : jwtBearerSettingsOptions.Value; |
| /// <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) |
There was a problem hiding this comment.
CreateTokenAsync does not enforce the interface contract that absoluteExpiration must be >= DateTime.UtcNow (see IJwtBearerService XML docs). If a past absoluteExpiration is provided, the method will mint an already-expired token rather than throwing ArgumentException as documented.
JwtBearerServicewasinternal, preventing subclassing for custom JWT configurations or overriding default behavior.internaltopublicCreateTokenAsync,ValidateTokenAsync,RefreshTokenAsync) markedvirtualjwtBearerSettingschanged from aprivatefield to aprotectedread-only property (JwtBearerSettings) for subclass accessOriginal prompt
📱 Kick off Copilot coding agent tasks wherever you are with GitHub Mobile, available on iOS and Android.