using System.Security.Claims; using System.Text; using Domain.Exceptions; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; using JwtRegisteredClaimNames = System.IdentityModel.Tokens.Jwt.JwtRegisteredClaimNames; namespace Features.Users.Services; /// /// Generates and validates HMAC-SHA256 signed JWTs using . /// public class JwtInfrastructure : ITokenInfrastructure { /// /// /// Sets registered claims sub (userId), unique_name (username), iat (current UTC time), /// exp (expiry), and jti (a newly generated GUID). /// /// The symmetric secret used to sign the token, encoded as UTF-8 bytes. public string GenerateJwt(Guid userId, string username, DateTime expiry, string secret) { JsonWebTokenHandler handler = new(); byte[] key = Encoding.UTF8.GetBytes(secret); List claims = new() { new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()), new Claim(JwtRegisteredClaimNames.UniqueName, username), new Claim( JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString() ), new Claim( JwtRegisteredClaimNames.Exp, new DateTimeOffset(expiry).ToUnixTimeSeconds().ToString() ), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), }; SecurityTokenDescriptor tokenDescriptor = new() { Subject = new ClaimsIdentity(claims), Expires = expiry, SigningCredentials = new SigningCredentials( new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256 ), }; return handler.CreateToken(tokenDescriptor); } /// /// Issuer and audience validation are disabled; only the signature and lifetime are validated. /// The symmetric secret used to verify the token's signature, encoded as UTF-8 bytes. /// /// Thrown when the token is invalid, has no claims identity, is expired, or otherwise fails validation. /// public async Task ValidateJwtAsync(string token, string secret) { JsonWebTokenHandler handler = new(); byte[] keyBytes = Encoding.UTF8.GetBytes(secret); TokenValidationParameters parameters = new() { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = true, IssuerSigningKey = new SymmetricSecurityKey(keyBytes), }; try { TokenValidationResult? result = await handler.ValidateTokenAsync(token, parameters); if (!result.IsValid || result.ClaimsIdentity == null) throw new UnauthorizedAccessException(); return new ClaimsPrincipal(result.ClaimsIdentity); } catch (Exception) { throw new UnauthorizedException("Invalid token"); } } }