diff --git a/src/MaksIT.Core.Tests/Security/JwtGeneratorTests.cs b/src/MaksIT.Core.Tests/Security/JwtGeneratorTests.cs
index f3c6b82..ba5af52 100644
--- a/src/MaksIT.Core.Tests/Security/JwtGeneratorTests.cs
+++ b/src/MaksIT.Core.Tests/Security/JwtGeneratorTests.cs
@@ -14,7 +14,7 @@ public class JwtGeneratorTests {
[Fact]
public void GenerateToken_ShouldReturnValidToken() {
// Act
- var token = JwtGenerator.GenerateToken(Secret, Issuer, Audience, Expiration, Username, Roles);
+ var (token, jwtTokenClaims) = JwtGenerator.GenerateToken(Secret, Issuer, Audience, Expiration, Username, Roles);
// Assert
Assert.False(string.IsNullOrEmpty(token));
@@ -23,7 +23,7 @@ public class JwtGeneratorTests {
[Fact]
public void ValidateToken_ShouldReturnClaimsPrincipal_WhenTokenIsValid() {
// Arrange
- var token = JwtGenerator.GenerateToken(Secret, Issuer, Audience, Expiration, Username, Roles);
+ var (token, _) = JwtGenerator.GenerateToken(Secret, Issuer, Audience, Expiration, Username, Roles);
// Act
var jwtTokenClaims = JwtGenerator.ValidateToken(Secret, Issuer, Audience, token);
@@ -55,4 +55,16 @@ public class JwtGeneratorTests {
// Assert
Assert.False(string.IsNullOrEmpty(refreshToken));
}
+
+ [Fact]
+ public void GenerateSecret_ShouldReturnDifferentValuesOnSubsequentCalls() {
+ // Act
+ string secret1 = JwtGenerator.GenerateSecret();
+ string secret2 = JwtGenerator.GenerateSecret();
+
+ // Assert
+ Assert.False(string.IsNullOrEmpty(secret1));
+ Assert.False(string.IsNullOrEmpty(secret2));
+ Assert.NotEqual(secret1, secret2); // Ensure the secrets are unique
+ }
}
diff --git a/src/MaksIT.Core/MaksIT.Core.csproj b/src/MaksIT.Core/MaksIT.Core.csproj
index 58ba4c4..0f86869 100644
--- a/src/MaksIT.Core/MaksIT.Core.csproj
+++ b/src/MaksIT.Core/MaksIT.Core.csproj
@@ -8,7 +8,7 @@
MaksIT.Core
- 1.0.4
+ 1.0.5
Maksym Sadovnychyy
MAKS-IT
MaksIT.Core
diff --git a/src/MaksIT.Core/Security/JwtGenerator.cs b/src/MaksIT.Core/Security/JwtGenerator.cs
index 0ff8de7..ea7907c 100644
--- a/src/MaksIT.Core/Security/JwtGenerator.cs
+++ b/src/MaksIT.Core/Security/JwtGenerator.cs
@@ -1,11 +1,11 @@
-using System.Text;
+using System;
+using System.Text;
+using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.IdentityModel.Tokens.Jwt;
-
using Microsoft.IdentityModel.Tokens;
-
namespace MaksIT.Core.Security;
public class JWTTokenClaims {
@@ -15,10 +15,9 @@ public class JWTTokenClaims {
public DateTime? ExpiresAt { get; set; }
}
-
public static class JwtGenerator {
public static (string, JWTTokenClaims) GenerateToken(string secret, string issuer, string audience, double expiration, string username, List roles) {
- var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
+ var secretKey = GetSymmetricSecurityKey(secret);
var credentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
var issuedAt = DateTime.UtcNow;
@@ -26,26 +25,24 @@ public static class JwtGenerator {
var claims = new List
{
- new Claim(ClaimTypes.Name, username),
- new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
- new Claim(JwtRegisteredClaimNames.Iat, new DateTimeOffset(issuedAt).ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64),
- new Claim(JwtRegisteredClaimNames.Exp, new DateTimeOffset(expiresAt).ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
- };
+ new Claim(ClaimTypes.Name, username),
+ new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
+ new Claim(JwtRegisteredClaimNames.Iat, new DateTimeOffset(issuedAt).ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64),
+ new Claim(JwtRegisteredClaimNames.Exp, new DateTimeOffset(expiresAt).ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
+ };
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
- var token = new JwtSecurityToken(
+ var tokenDescriptor = new JwtSecurityToken(
issuer: issuer,
audience: audience,
claims: claims,
- expires: DateTime.Now.AddMinutes(Convert.ToDouble(expiration)),
+ expires: expiresAt,
signingCredentials: credentials
);
- var jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
+ var jwtToken = new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
-
- // Create the JWTTokenClaims object
var tokenClaims = new JWTTokenClaims {
Username = username,
Roles = roles,
@@ -56,13 +53,13 @@ public static class JwtGenerator {
return (jwtToken, tokenClaims);
}
-
+ public static string GenerateSecret(int keySize = 32) => Convert.ToBase64String(GetRandomBytes(keySize));
public static JWTTokenClaims? ValidateToken(string secret, string issuer, string audience, string token) {
try {
var key = Encoding.UTF8.GetBytes(secret);
-
var tokenHandler = new JwtSecurityTokenHandler();
+
var validationParameters = new TokenValidationParameters {
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
@@ -76,34 +73,45 @@ public static class JwtGenerator {
var principal = tokenHandler.ValidateToken(token, validationParameters, out var validatedToken);
- var username = principal?.Identity?.Name;
- var roles = principal?.Claims.Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value).ToList();
+ // Validate the algorithm used
+ if (validatedToken is JwtSecurityToken jwtToken && jwtToken.Header.Alg != SecurityAlgorithms.HmacSha256)
+ throw new SecurityTokenException("Invalid token algorithm");
-
- var issuedAtClaim = principal?.Claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Iat)?.Value;
- var expiresAtClaim = principal?.Claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Exp)?.Value;
-
- DateTime? issuedAt = issuedAtClaim != null ? DateTimeOffset.FromUnixTimeSeconds(long.Parse(issuedAtClaim)).UtcDateTime : (DateTime?)null;
- DateTime? expiresAt = expiresAtClaim != null ? DateTimeOffset.FromUnixTimeSeconds(long.Parse(expiresAtClaim)).UtcDateTime : (DateTime?)null;
-
-
- return new JWTTokenClaims {
- Username = username,
- Roles = roles,
- IssuedAt = issuedAt,
- ExpiresAt = expiresAt
- };
+ return ExtractClaims(principal);
}
catch {
return null;
}
}
- public static string GenerateRefreshToken() {
- var randomNumber = new byte[32];
- using (var rng = RandomNumberGenerator.Create()) {
- rng.GetBytes(randomNumber);
- return Convert.ToBase64String(randomNumber);
- }
+ public static string GenerateRefreshToken() => Convert.ToBase64String(GetRandomBytes(32));
+
+ // Private helper method to extract claims
+ private static JWTTokenClaims? ExtractClaims(ClaimsPrincipal principal) {
+ var username = principal.Identity?.Name;
+ var roles = principal.Claims.Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value).ToList();
+
+ var issuedAtClaim = principal.Claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Iat)?.Value;
+ var expiresAtClaim = principal.Claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Exp)?.Value;
+
+ DateTime? issuedAt = issuedAtClaim != null ? DateTimeOffset.FromUnixTimeSeconds(long.Parse(issuedAtClaim)).UtcDateTime : (DateTime?)null;
+ DateTime? expiresAt = expiresAtClaim != null ? DateTimeOffset.FromUnixTimeSeconds(long.Parse(expiresAtClaim)).UtcDateTime : (DateTime?)null;
+
+ return new JWTTokenClaims {
+ Username = username,
+ Roles = roles,
+ IssuedAt = issuedAt,
+ ExpiresAt = expiresAt
+ };
+ }
+
+ // Private helper method to get a symmetric security key
+ private static SymmetricSecurityKey GetSymmetricSecurityKey(string secret) => new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
+
+ // Private helper method for generating random bytes
+ private static byte[] GetRandomBytes(int size) {
+ var bytes = new byte[size];
+ RandomNumberGenerator.Fill(bytes);
+ return bytes;
}
}