Skip to content

Make JwtBearerService public#192

Merged
marcominerva merged 7 commits intomasterfrom
develop
Apr 9, 2026
Merged

Make JwtBearerService public#192
marcominerva merged 7 commits intomasterfrom
develop

Conversation

@marcominerva
Copy link
Copy Markdown
Owner

This pull request primarily upgrades several NuGet package dependencies across the solution and refactors the JwtBearerService class to improve clarity, extensibility, and documentation. The changes focus on keeping dependencies up to date and making the JWT Bearer service implementation more robust and maintainable.

Dependency Updates:

  • Upgraded Microsoft.AspNetCore.OpenApi to version 10.0.5 and Swashbuckle.AspNetCore.SwaggerUI to 10.1.7 in all sample project files under both Controllers and MinimalApis folders. [1] [2] [3]
  • Upgraded Swashbuckle.AspNetCore and Swashbuckle.AspNetCore.SwaggerGen to version 10.1.7 in relevant sample and library projects. [1] [2]
  • Upgraded Microsoft.AspNetCore.Authentication.JwtBearer to version 10.0.5 in SimpleAuthentication.Abstractions.

JwtBearerService Refactoring and Improvements:

  • Changed JwtBearerService from internal to public, added XML documentation, and made key methods (CreateTokenAsync, ValidateTokenAsync, RefreshTokenAsync) virtual for easier extension and testing. [1] [2] [3]
  • Replaced usage of the local jwtBearerSettings field with the protected property JwtBearerSettings throughout the class for consistency and improved encapsulation. Added null checks and argument validation. [1] [2] [3]

Behavioral Change:

  • Updated JWT Bearer authentication setup to always validate token lifetime (ValidateLifetime = true) instead of conditionally based on expiration time settings.

Copilot AI and others added 7 commits March 12, 2026 14:50
Co-authored-by: marcominerva <3522534+marcominerva@users.noreply.github.com>
…property

Co-authored-by: marcominerva <3522534+marcominerva@users.noreply.github.com>
…ce-public

Make JwtBearerService public with virtual methods for extensibility
- Throw ArgumentNullException if JwtBearerSettings options are null in JwtBearerService constructor.
- Always enable token lifetime validation in JWT Bearer authentication, regardless of ExpirationTime setting.
Bump Swashbuckle.AspNetCore.SwaggerUI and SwaggerGen from 10.1.5 to 10.1.7 across all projects. Update Swashbuckle.AspNetCore in Net8JwtBearerSample to 10.1.7. Also update Microsoft.AspNetCore.Authentication.JwtBearer and Microsoft.AspNetCore.OpenApi for .NET 10.0 to their latest patch versions. No other changes included.
Add a check to ensure absoluteExpiration is not earlier than the current UTC time when creating a JWT token. Throw an ArgumentException if an invalid expiration is provided to prevent issuing already-expired tokens.
Copilot AI review requested due to automatic review settings April 9, 2026 10:02
@marcominerva marcominerva merged commit 8735ca8 into master Apr 9, 2026
8 checks passed
Copy link
Copy Markdown

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

This PR updates several ASP.NET Core / Swashbuckle NuGet dependencies across the library and samples, and makes JwtBearerService a public, more extensible implementation of IJwtBearerService with updated documentation and settings encapsulation.

Changes:

  • Bump Microsoft.AspNetCore.OpenApi, Microsoft.AspNetCore.Authentication.JwtBearer, and Swashbuckle package versions across projects/samples.
  • Refactor JwtBearerService to be public, add XML docs, and make key methods virtual.
  • Change JWT bearer middleware configuration to always validate token lifetime (ValidateLifetime = true).

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/SimpleAuthentication/SimpleAuthenticationExtensions.cs Always enables JWT lifetime validation in the middleware setup.
src/SimpleAuthentication/SimpleAuthentication.csproj Updates Microsoft.AspNetCore.OpenApi package version.
src/SimpleAuthentication/JwtBearer/JwtBearerService.cs Makes service public/virtual, centralizes settings access, adds validation/docs.
src/SimpleAuthentication.Swashbuckle/SimpleAuthentication.Swashbuckle.csproj Updates Swashbuckle.AspNetCore.SwaggerGen version.
src/SimpleAuthentication.Abstractions/SimpleAuthentication.Abstractions.csproj Updates Microsoft.AspNetCore.Authentication.JwtBearer version.
samples/MinimalApis/Net8JwtBearerSample/Net8JwtBearerSample.csproj Updates Swashbuckle dependency in minimal API sample.
samples/MinimalApis/JwtBearerSample/JwtBearerSample.csproj Updates OpenAPI + SwaggerUI dependencies in minimal API sample.
samples/MinimalApis/BasicAuthenticationSample/BasicAuthenticationSample.csproj Updates OpenAPI + SwaggerUI dependencies in minimal API sample.
samples/MinimalApis/ApiKeySample/ApiKeySample.csproj Updates OpenAPI + SwaggerUI dependencies in minimal API sample.
samples/Controllers/JwtBearerSample/JwtBearerSample.csproj Updates OpenAPI + SwaggerUI dependencies in controllers sample.
samples/Controllers/BasicAuthenticationSample/BasicAuthenticationSample.csproj Updates OpenAPI + SwaggerUI dependencies in controllers sample.
samples/Controllers/ApiKeySample/ApiKeySample.csproj Updates OpenAPI + SwaggerUI dependencies in controllers sample.

throw new ArgumentException("The expiration date must be greater than or equal to the current date and time.", nameof(absoluteExpiration));
}

claims ??= [];
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

claims ??= [] will most likely materialize as an array assigned to IList<Claim>. Arrays implement IList<T> but are fixed-size, so subsequent calls to claims.Update(...) (which uses Add/Remove) can throw NotSupportedException when claims is initially null. Use a mutable collection (e.g., initialize to a List<Claim>) before mutating the list.

Suggested change
claims ??= [];
claims = claims switch
{
null => new global::System.Collections.Generic.List<Claim>(),
{ IsReadOnly: true } => new global::System.Collections.Generic.List<Claim>(claims),
_ => claims
};

Copilot uses AI. Check for mistakes.
Comment on lines +18 to 19
protected JwtBearerSettings JwtBearerSettings { get; } = jwtBearerSettingsOptions?.Value ?? throw new ArgumentNullException(nameof(jwtBearerSettingsOptions));

Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

JwtBearerSettings initialization uses jwtBearerSettingsOptions?.Value ?? throw new ArgumentNullException(nameof(jwtBearerSettingsOptions)), which throws an ArgumentNullException for the options parameter even when jwtBearerSettingsOptions is non-null but .Value is null. Consider validating jwtBearerSettingsOptions explicitly (per project style) and failing with a more accurate exception if the bound settings instance is null.

Suggested change
protected JwtBearerSettings JwtBearerSettings { get; } = jwtBearerSettingsOptions?.Value ?? throw new ArgumentNullException(nameof(jwtBearerSettingsOptions));
protected JwtBearerSettings JwtBearerSettings { get; } = GetJwtBearerSettings(jwtBearerSettingsOptions);
private static JwtBearerSettings GetJwtBearerSettings(IOptions<JwtBearerSettings> jwtBearerSettingsOptions)
{
ArgumentNullException.ThrowIfNull(jwtBearerSettingsOptions);
return jwtBearerSettingsOptions.Value ?? throw new InvalidOperationException($"{nameof(JwtBearerSettings)} options must be configured.");
}

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +28
var now = DateTime.UtcNow;

if (absoluteExpiration.HasValue && absoluteExpiration.Value < now)
{
throw new ArgumentException("The expiration date must be greater than or equal to the current date and time.", nameof(absoluteExpiration));
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

The absoluteExpiration validation compares a user-provided DateTime against DateTime.UtcNow without normalizing time zones. If callers pass a DateTime with Kind = Local/Unspecified, the comparison can be wrong (since DateTime comparisons don't convert between kinds). Consider requiring UTC explicitly (e.g., validate Kind and/or convert to UTC) and clarifying the exception message accordingly.

Copilot uses AI. Check for mistakes.
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;
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

RefreshTokenAsync assumes the validated token always contains a claim with Type == JwtBearerSettings.NameClaimType and uses First(...), which will throw InvalidOperationException if the claim is missing. Since ValidateTokenAsync can succeed even when that claim isn't present, consider handling the missing-claim case explicitly and throwing a more appropriate exception (e.g., SecurityTokenException/ArgumentException) with a clear message.

Suggested change
var userName = claims.First(c => c.Type == JwtBearerSettings.NameClaimType).Value;
var nameClaim = claims.FirstOrDefault(c => c.Type == JwtBearerSettings.NameClaimType);
if (string.IsNullOrEmpty(nameClaim?.Value))
{
throw new SecurityTokenException($"Token does not contain the required '{JwtBearerSettings.NameClaimType}' claim.");
}
var userName = nameClaim.Value;

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