(feature): JWT token handle methods

This commit is contained in:
Maksym Sadovnychyy 2024-09-26 21:02:14 +02:00
parent 307cce4606
commit e4da2e68b3
4 changed files with 193 additions and 1 deletions

View File

@ -13,6 +13,7 @@ MaksIT.Core is a collection of helper methods and extensions for .NET projects,
- [Password Hasher](#password-hasher)
- [DataTable Extensions](#datatable-extensions)
- [DateTime Extensions](#datetime-extensions)
- [JWT Token Generation and Validation](#jwt-token-generation-and-validation)
- [Available Methods](#available-methods)
- [Enumeration Methods](#enumeration-methods)
- [Guid Methods](#guid-methods)
@ -21,6 +22,7 @@ MaksIT.Core is a collection of helper methods and extensions for .NET projects,
- [Security Methods](#security-methods)
- [DataTable Methods](#datatable-methods)
- [DateTime Methods](#datetime-methods)
- [JWT Methods](#jwt-methods)
- [Contributing](#contributing)
- [License](#license)
- [Contact](#contact)
@ -166,6 +168,50 @@ DateTime nextThursday = DateTime.Now.NextWeekday(DayOfWeek.Thursday);
Console.WriteLine(nextThursday); // Output: Date of the next Thursday
```
### JWT Token Generation and Validation
The `JwtGenerator` class provides methods for generating and validating JWT tokens, as well as generating refresh tokens. This is useful for implementing secure authentication and authorization mechanisms in your applications.
#### Generate a JWT Token
```csharp
using MaksIT.Core.Security;
string secret = "your_secret_key";
string issuer = "your_issuer";
string audience = "your_audience";
double expiration = 30; // Token expiration in minutes
string username = "user123";
List<string> roles = new List<string> { "Admin", "User" };
string token = JwtGenerator.GenerateToken(secret, issuer, audience, expiration, username, roles);
Console.WriteLine("Generated JWT Token: " + token);
```
#### Validate a JWT Token
```csharp
string tokenToValidate = "your_jwt_token";
JWTTokenClaims? claims = JwtGenerator.ValidateToken(secret, issuer, audience, tokenToValidate);
if (claims != null)
{
Console.WriteLine($"Username: {claims.Username}");
Console.WriteLine("Roles: " + string.Join(", ", claims.Roles));
}
else
{
Console.WriteLine("Invalid token.");
}
```
#### Generate a Refresh Token
```csharp
string refreshToken = JwtGenerator.GenerateRefreshToken();
Console.WriteLine("Generated Refresh Token: " + refreshToken);
```
## Available Methods
### Enumeration Methods
@ -241,6 +287,12 @@ Console.WriteLine(nextThursday); // Output: Date of the next Thursday
- **IsSameMonth(this DateTime date, DateTime targetDate)**: Checks if two dates are in the same month and year.
- **GetDifferenceInYears(this DateTime startDate, DateTime endDate)**: Returns the difference in years between two dates.
### JWT Methods
- **GenerateToken**: Generates jwt-token-generation-and-validationa JWT token.
- **ValidateToken**: Validates a JWT token and extracts claims.
- **GenerateRefreshToken**: Generates a secure refresh token.
## Contributing
Contributions to this project are welcome! Please fork the repository and submit a pull request with your changes. If you encounter any issues or have feature requests, feel free to open an issue on GitHub.

View File

@ -0,0 +1,58 @@
using MaksIT.Core.Security;
namespace MaksIT.Core.Tests.Security;
public class JwtGeneratorTests {
private const string Secret = "supersecretkey12345678901234567890";
private const string Issuer = "testIssuer";
private const string Audience = "testAudience";
private const double Expiration = 30; // 30 minutes
private const string Username = "testUser";
private readonly List<string> Roles = new List<string> { "Admin", "User" };
[Fact]
public void GenerateToken_ShouldReturnValidToken() {
// Act
var token = JwtGenerator.GenerateToken(Secret, Issuer, Audience, Expiration, Username, Roles);
// Assert
Assert.False(string.IsNullOrEmpty(token));
}
[Fact]
public void ValidateToken_ShouldReturnClaimsPrincipal_WhenTokenIsValid() {
// Arrange
var token = JwtGenerator.GenerateToken(Secret, Issuer, Audience, Expiration, Username, Roles);
// Act
var jwtTokenClaims = JwtGenerator.ValidateToken(Secret, Issuer, Audience, token);
// Assert
Assert.NotNull(jwtTokenClaims);
Assert.Equal(Username, jwtTokenClaims.Username);
Assert.Contains(jwtTokenClaims.Roles ?? new List<string>(), c => c == "Admin");
Assert.Contains(jwtTokenClaims.Roles ?? new List<string>(), c => c == "User");
}
[Fact]
public void ValidateToken_ShouldReturnNull_WhenTokenIsInvalid() {
// Arrange
var invalidToken = "invalidToken";
// Act
var principal = JwtGenerator.ValidateToken(Secret, Issuer, Audience, invalidToken);
// Assert
Assert.Null(principal);
}
[Fact]
public void GenerateRefreshToken_ShouldReturnNonEmptyString() {
// Act
var refreshToken = JwtGenerator.GenerateRefreshToken();
// Assert
Assert.False(string.IsNullOrEmpty(refreshToken));
}
}

View File

@ -8,7 +8,7 @@
<!-- NuGet package metadata -->
<PackageId>MaksIT.Core</PackageId>
<Version>1.0.2</Version>
<Version>1.0.3</Version>
<Authors>Maksym Sadovnychyy</Authors>
<Company>MAKS-IT</Company>
<Product>MaksIT.Core</Product>
@ -26,5 +26,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="8.0.8" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.1.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.1.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,80 @@
using System.Text;
using System.Security.Claims;
using System.Security.Cryptography;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
namespace MaksIT.Core.Security;
public class JWTTokenClaims {
public required string? Username { get; set; }
public required List<string>? Roles { get; set; }
}
public static class JwtGenerator {
public static string GenerateToken(string secret, string issuer, string audience, double expiration, string username, List<string> roles) {
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
var credentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
var token = new JwtSecurityToken(
issuer: issuer,
audience: audience,
claims: claims,
expires: DateTime.Now.AddMinutes(Convert.ToDouble(expiration)),
signingCredentials: credentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
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),
ValidateIssuer = true,
ValidIssuer = issuer,
ValidateAudience = true,
ValidAudience = audience,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
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();
return new JWTTokenClaims {
Username = username,
Roles = roles
};
}
catch {
return null;
}
}
public static string GenerateRefreshToken() {
var randomNumber = new byte[32];
using (var rng = RandomNumberGenerator.Create()) {
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
}
}