(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] [Fact]
public void GenerateToken_ShouldReturnValidToken() { public void GenerateToken_ShouldReturnValidToken() {
// Act // Act
var token = JwtGenerator.GenerateToken(Secret, Issuer, Audience, Expiration, Username, Roles); var (token, jwtTokenClaims) = JwtGenerator.GenerateToken(Secret, Issuer, Audience, Expiration, Username, Roles);
// Assert // Assert
Assert.False(string.IsNullOrEmpty(token)); Assert.False(string.IsNullOrEmpty(token));
@ -23,7 +23,7 @@ public class JwtGeneratorTests {
[Fact] [Fact]
public void ValidateToken_ShouldReturnClaimsPrincipal_WhenTokenIsValid() { public void ValidateToken_ShouldReturnClaimsPrincipal_WhenTokenIsValid() {
// Arrange // Arrange
var token = JwtGenerator.GenerateToken(Secret, Issuer, Audience, Expiration, Username, Roles); var (token, _) = JwtGenerator.GenerateToken(Secret, Issuer, Audience, Expiration, Username, Roles);
// Act // Act
var jwtTokenClaims = JwtGenerator.ValidateToken(Secret, Issuer, Audience, token); var jwtTokenClaims = JwtGenerator.ValidateToken(Secret, Issuer, Audience, token);
@ -55,4 +55,16 @@ public class JwtGeneratorTests {
// Assert // Assert
Assert.False(string.IsNullOrEmpty(refreshToken)); 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 --> <!-- NuGet package metadata -->
<PackageId>MaksIT.Core</PackageId> <PackageId>MaksIT.Core</PackageId>
<Version>1.0.4</Version> <Version>1.0.5</Version>
<Authors>Maksym Sadovnychyy</Authors> <Authors>Maksym Sadovnychyy</Authors>
<Company>MAKS-IT</Company> <Company>MAKS-IT</Company>
<Product>MaksIT.Core</Product> <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.Claims;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
namespace MaksIT.Core.Security; namespace MaksIT.Core.Security;
public class JWTTokenClaims { public class JWTTokenClaims {
@ -15,10 +15,9 @@ public class JWTTokenClaims {
public DateTime? ExpiresAt { get; set; } public DateTime? ExpiresAt { get; set; }
} }
public static class JwtGenerator { public static class JwtGenerator {
public static (string, JWTTokenClaims) GenerateToken(string secret, string issuer, string audience, double expiration, string username, List<string> roles) { 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 credentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
var issuedAt = DateTime.UtcNow; var issuedAt = DateTime.UtcNow;
@ -26,26 +25,24 @@ public static class JwtGenerator {
var claims = new List<Claim> var claims = new List<Claim>
{ {
new Claim(ClaimTypes.Name, username), new Claim(ClaimTypes.Name, username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, new DateTimeOffset(issuedAt).ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64), new Claim(JwtRegisteredClaimNames.Iat, new DateTimeOffset(issuedAt).ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64),
new Claim(JwtRegisteredClaimNames.Exp, new DateTimeOffset(expiresAt).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))); claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
var token = new JwtSecurityToken( var tokenDescriptor = new JwtSecurityToken(
issuer: issuer, issuer: issuer,
audience: audience, audience: audience,
claims: claims, claims: claims,
expires: DateTime.Now.AddMinutes(Convert.ToDouble(expiration)), expires: expiresAt,
signingCredentials: credentials signingCredentials: credentials
); );
var jwtToken = new JwtSecurityTokenHandler().WriteToken(token); var jwtToken = new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
// Create the JWTTokenClaims object
var tokenClaims = new JWTTokenClaims { var tokenClaims = new JWTTokenClaims {
Username = username, Username = username,
Roles = roles, Roles = roles,
@ -56,13 +53,13 @@ public static class JwtGenerator {
return (jwtToken, tokenClaims); 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) { public static JWTTokenClaims? ValidateToken(string secret, string issuer, string audience, string token) {
try { try {
var key = Encoding.UTF8.GetBytes(secret); var key = Encoding.UTF8.GetBytes(secret);
var tokenHandler = new JwtSecurityTokenHandler(); var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters { var validationParameters = new TokenValidationParameters {
ValidateIssuerSigningKey = true, ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key), IssuerSigningKey = new SymmetricSecurityKey(key),
@ -76,34 +73,45 @@ public static class JwtGenerator {
var principal = tokenHandler.ValidateToken(token, validationParameters, out var validatedToken); var principal = tokenHandler.ValidateToken(token, validationParameters, out var validatedToken);
var username = principal?.Identity?.Name; // Validate the algorithm used
var roles = principal?.Claims.Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value).ToList(); if (validatedToken is JwtSecurityToken jwtToken && jwtToken.Header.Alg != SecurityAlgorithms.HmacSha256)
throw new SecurityTokenException("Invalid token algorithm");
return ExtractClaims(principal);
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
};
} }
catch { catch {
return null; return null;
} }
} }
public static string GenerateRefreshToken() { public static string GenerateRefreshToken() => Convert.ToBase64String(GetRandomBytes(32));
var randomNumber = new byte[32];
using (var rng = RandomNumberGenerator.Create()) { // Private helper method to extract claims
rng.GetBytes(randomNumber); private static JWTTokenClaims? ExtractClaims(ClaimsPrincipal principal) {
return Convert.ToBase64String(randomNumber); 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;
} }
} }