Skip to content

Make JwtBearerService public with virtual methods for extensibility#191

Open
Copilot wants to merge 3 commits intodevelopfrom
copilot/make-jwtbearerservice-public
Open

Make JwtBearerService public with virtual methods for extensibility#191
Copilot wants to merge 3 commits intodevelopfrom
copilot/make-jwtbearerservice-public

Conversation

Copy link
Contributor

Copilot AI commented Mar 12, 2026

JwtBearerService was internal, preventing subclassing for custom JWT configurations or overriding default behavior.

  • Class visibility changed from internal to public
  • All public methods (CreateTokenAsync, ValidateTokenAsync, RefreshTokenAsync) marked virtual
  • jwtBearerSettings changed from a private field to a protected read-only property (JwtBearerSettings) for subclass access
  • Added XML documentation for newly public API surface
public class CustomJwtService : JwtBearerService
{
    public CustomJwtService(IOptions<JwtBearerSettings> options) : base(options) { }

    public override async Task<ClaimsPrincipal> ValidateTokenAsync(string token, bool validateLifetime = true)
    {
        // Custom validation logic
        var principal = await base.ValidateTokenAsync(token, validateLifetime);
        // Additional checks...
        return principal;
    }
}
Original prompt

This section details on the original issue you should resolve

<issue_title>Make JwtBearerService public and its methods virtual for extensibility</issue_title>
<issue_description>## Feature Request

Please make the JwtBearerService class public. This would allow developers to create multiple wrappers with different configurations, enabling greater flexibility (for example, instantiating it as a KeyService).

Additionally, make all public methods of JwtBearerService virtual so they can be overridden. This will improve extensibility and allow advanced users to customize the authentication logic as needed.

Rationale:

  • Public visibility allows users to instantiate JwtBearerService as needed in various scenarios.
  • Virtual methods facilitate extension and customization without changes to the core package.

Example Use Cases:

  • Creating different services for distinct JWT configurations
  • Overriding default behavior for custom key validation

If this change is acceptable, please outline any potential impacts on the public API and document the new usage pattern.</issue_description>

Comments on the Issue (you are @copilot in this section)


📱 Kick off Copilot coding agent tasks wherever you are with GitHub Mobile, available on iOS and Android.

Co-authored-by: marcominerva <3522534+marcominerva@users.noreply.github.com>
Copilot AI changed the title [WIP] Make JwtBearerService public and methods virtual for extensibility Make JwtBearerService public with virtual methods for extensibility Mar 12, 2026
…property

Co-authored-by: marcominerva <3522534+marcominerva@users.noreply.github.com>
@marcominerva marcominerva marked this pull request as ready for review March 12, 2026 16:03
Copilot AI review requested due to automatic review settings March 12, 2026 16:03
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 JwtBearerService visibility from internal to public.
  • Marked CreateTokenAsync, ValidateTokenAsync, and RefreshTokenAsync as virtual for overriding.
  • Replaced the private settings field with a protected JwtBearerSettings property 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;
Copy link

Copilot AI Mar 12, 2026

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.

Suggested change
protected JwtBearerSettings JwtBearerSettings { get; } = jwtBearerSettingsOptions.Value;
protected JwtBearerSettings JwtBearerSettings { get; } =
jwtBearerSettingsOptions is null
? throw new ArgumentNullException(nameof(jwtBearerSettingsOptions))
: jwtBearerSettingsOptions.Value;

Copilot uses AI. Check for mistakes.
Comment on lines +20 to +37
/// <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)
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants