(feature): test updates, jwt secret generation method
This commit is contained in:
parent
4a6e89c350
commit
f80aa1dd95
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user