diff --git a/samples/Controllers/ApiKeySample/ApiKeySample.csproj b/samples/Controllers/ApiKeySample/ApiKeySample.csproj
index 4f40513..3052b54 100644
--- a/samples/Controllers/ApiKeySample/ApiKeySample.csproj
+++ b/samples/Controllers/ApiKeySample/ApiKeySample.csproj
@@ -7,8 +7,8 @@
-
-
+
+
diff --git a/samples/Controllers/BasicAuthenticationSample/BasicAuthenticationSample.csproj b/samples/Controllers/BasicAuthenticationSample/BasicAuthenticationSample.csproj
index 4f40513..3052b54 100644
--- a/samples/Controllers/BasicAuthenticationSample/BasicAuthenticationSample.csproj
+++ b/samples/Controllers/BasicAuthenticationSample/BasicAuthenticationSample.csproj
@@ -7,8 +7,8 @@
-
-
+
+
diff --git a/samples/Controllers/JwtBearerSample/JwtBearerSample.csproj b/samples/Controllers/JwtBearerSample/JwtBearerSample.csproj
index 4f40513..3052b54 100644
--- a/samples/Controllers/JwtBearerSample/JwtBearerSample.csproj
+++ b/samples/Controllers/JwtBearerSample/JwtBearerSample.csproj
@@ -7,8 +7,8 @@
-
-
+
+
diff --git a/samples/MinimalApis/ApiKeySample/ApiKeySample.csproj b/samples/MinimalApis/ApiKeySample/ApiKeySample.csproj
index 84d35a1..8e34124 100644
--- a/samples/MinimalApis/ApiKeySample/ApiKeySample.csproj
+++ b/samples/MinimalApis/ApiKeySample/ApiKeySample.csproj
@@ -7,8 +7,8 @@
-
-
+
+
diff --git a/samples/MinimalApis/BasicAuthenticationSample/BasicAuthenticationSample.csproj b/samples/MinimalApis/BasicAuthenticationSample/BasicAuthenticationSample.csproj
index 84d35a1..8e34124 100644
--- a/samples/MinimalApis/BasicAuthenticationSample/BasicAuthenticationSample.csproj
+++ b/samples/MinimalApis/BasicAuthenticationSample/BasicAuthenticationSample.csproj
@@ -7,8 +7,8 @@
-
-
+
+
diff --git a/samples/MinimalApis/JwtBearerSample/JwtBearerSample.csproj b/samples/MinimalApis/JwtBearerSample/JwtBearerSample.csproj
index 84d35a1..8e34124 100644
--- a/samples/MinimalApis/JwtBearerSample/JwtBearerSample.csproj
+++ b/samples/MinimalApis/JwtBearerSample/JwtBearerSample.csproj
@@ -7,8 +7,8 @@
-
-
+
+
diff --git a/samples/MinimalApis/Net8JwtBearerSample/Net8JwtBearerSample.csproj b/samples/MinimalApis/Net8JwtBearerSample/Net8JwtBearerSample.csproj
index 1c38ccb..17c6dda 100644
--- a/samples/MinimalApis/Net8JwtBearerSample/Net8JwtBearerSample.csproj
+++ b/samples/MinimalApis/Net8JwtBearerSample/Net8JwtBearerSample.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/src/SimpleAuthentication.Abstractions/SimpleAuthentication.Abstractions.csproj b/src/SimpleAuthentication.Abstractions/SimpleAuthentication.Abstractions.csproj
index 024ab07..ff7fe75 100644
--- a/src/SimpleAuthentication.Abstractions/SimpleAuthentication.Abstractions.csproj
+++ b/src/SimpleAuthentication.Abstractions/SimpleAuthentication.Abstractions.csproj
@@ -36,7 +36,7 @@
-
+
diff --git a/src/SimpleAuthentication.Swashbuckle/SimpleAuthentication.Swashbuckle.csproj b/src/SimpleAuthentication.Swashbuckle/SimpleAuthentication.Swashbuckle.csproj
index 19252c8..a493341 100644
--- a/src/SimpleAuthentication.Swashbuckle/SimpleAuthentication.Swashbuckle.csproj
+++ b/src/SimpleAuthentication.Swashbuckle/SimpleAuthentication.Swashbuckle.csproj
@@ -33,7 +33,7 @@
-
+
diff --git a/src/SimpleAuthentication/JwtBearer/JwtBearerService.cs b/src/SimpleAuthentication/JwtBearer/JwtBearerService.cs
index ba94857..53dac5f 100644
--- a/src/SimpleAuthentication/JwtBearer/JwtBearerService.cs
+++ b/src/SimpleAuthentication/JwtBearer/JwtBearerService.cs
@@ -6,27 +6,40 @@
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 ?? throw new ArgumentNullException(nameof(jwtBearerSettingsOptions));
- 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)
{
+ 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));
+ }
+
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 +48,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 +60,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 +85,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;
diff --git a/src/SimpleAuthentication/SimpleAuthentication.csproj b/src/SimpleAuthentication/SimpleAuthentication.csproj
index 7478133..bcf7d7b 100644
--- a/src/SimpleAuthentication/SimpleAuthentication.csproj
+++ b/src/SimpleAuthentication/SimpleAuthentication.csproj
@@ -35,7 +35,7 @@
-
+
diff --git a/src/SimpleAuthentication/SimpleAuthenticationExtensions.cs b/src/SimpleAuthentication/SimpleAuthenticationExtensions.cs
index e5d76d5..8ac0155 100644
--- a/src/SimpleAuthentication/SimpleAuthenticationExtensions.cs
+++ b/src/SimpleAuthentication/SimpleAuthenticationExtensions.cs
@@ -112,7 +112,7 @@ static void CheckAddJwtBearer(AuthenticationBuilder builder, IConfigurationSecti
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(settings.SecurityKey)),
RequireExpirationTime = true,
- ValidateLifetime = settings.ExpirationTime.GetValueOrDefault() > TimeSpan.Zero,
+ ValidateLifetime = true,
ClockSkew = settings.ClockSkew
};
});