(feature): test updates, jwt secret generation method

This commit is contained in:
Maksym Sadovnychyy 2024-09-28 23:24:27 +02:00
parent 4a6e89c350
commit f80aa1dd95
3 changed files with 62 additions and 42 deletions

View File

@ -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
}
}

View File

@ -8,7 +8,7 @@
<!-- NuGet package metadata -->
<PackageId>MaksIT.Core</PackageId>
<Version>1.0.4</Version>
<Version>1.0.5</Version>
<Authors>Maksym Sadovnychyy</Authors>
<Company>MAKS-IT</Company>
<Product>MaksIT.Core</Product>

View File

@ -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<string> roles) {
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
var secretKey = GetSymmetricSecurityKey(secret);
var credentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
var issuedAt = DateTime.UtcNow;
@ -34,18 +33,16 @@ public static class JwtGenerator {
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,17 +73,30 @@ 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");
return ExtractClaims(principal);
}
catch {
return null;
}
}
var issuedAtClaim = principal?.Claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Iat)?.Value;
var expiresAtClaim = principal?.Claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Exp)?.Value;
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,
@ -94,16 +104,14 @@ public static class JwtGenerator {
ExpiresAt = expiresAt
};
}
catch {
return null;
}
}
public static string GenerateRefreshToken() {
var randomNumber = new byte[32];
using (var rng = RandomNumberGenerator.Create()) {
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
// 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;
}
}