(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) - [Password Hasher](#password-hasher)
- [DataTable Extensions](#datatable-extensions) - [DataTable Extensions](#datatable-extensions)
- [DateTime Extensions](#datetime-extensions) - [DateTime Extensions](#datetime-extensions)
- [JWT Token Generation and Validation](#jwt-token-generation-and-validation)
- [Available Methods](#available-methods) - [Available Methods](#available-methods)
- [Enumeration Methods](#enumeration-methods) - [Enumeration Methods](#enumeration-methods)
- [Guid Methods](#guid-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) - [Security Methods](#security-methods)
- [DataTable Methods](#datatable-methods) - [DataTable Methods](#datatable-methods)
- [DateTime Methods](#datetime-methods) - [DateTime Methods](#datetime-methods)
- [JWT Methods](#jwt-methods)
- [Contributing](#contributing) - [Contributing](#contributing)
- [License](#license) - [License](#license)
- [Contact](#contact) - [Contact](#contact)
@ -166,6 +168,50 @@ DateTime nextThursday = DateTime.Now.NextWeekday(DayOfWeek.Thursday);
Console.WriteLine(nextThursday); // Output: Date of the next 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 ## Available Methods
### Enumeration 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. - **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. - **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 ## 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. 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 --> <!-- NuGet package metadata -->
<PackageId>MaksIT.Core</PackageId> <PackageId>MaksIT.Core</PackageId>
<Version>1.0.2</Version> <Version>1.0.3</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>
@ -26,5 +26,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="8.0.8" /> <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> </ItemGroup>
</Project> </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);
}
}
}