2025-06-15 14:24:53 -07:00

177 lines
6.1 KiB
C#

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
namespace qtc_api.Services.TokenService
{
public class TokenService : ITokenService
{
private readonly IConfiguration _configuration;
private readonly DataContext _dataContext;
public TokenService(IConfiguration configuration, DataContext dataContext)
{
_configuration = configuration;
_dataContext = dataContext;
}
public async Task<ServiceResponse<string>> GenerateAccessTokenAndRefreshToken(User user, bool generateRefToken, bool remember)
{
var serviceResponse = new ServiceResponse<string>();
// Generate JWT Access Token
List<Claim> claims = new List<Claim>()
{
new Claim(ClaimTypes.Hash, user.Id),
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.Email, user.Email),
new Claim(ClaimTypes.Role, user.Role)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.GetSection("Jwt:Key").Value!));
var issuer = _configuration["Jwt:Issuer"];
var audience = _configuration["Jwt:Audience"];
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: issuer,
audience: audience,
claims: claims,
expires: DateTime.UtcNow.AddHours(1),
signingCredentials: creds
);
var jwt = new JwtSecurityTokenHandler().WriteToken(token);
serviceResponse.Data = jwt;
// Generate and Store Refresh Token
if (generateRefToken)
{
var random = new byte[32];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(random);
}
RefreshToken refToken = new RefreshToken()
{
ID = LongRandom(1, 900000000000000000, new Random()).ToString(),
UserID = user.Id,
Token = Convert.ToBase64String(random)
};
if (remember) refToken.Expires = DateTime.UtcNow.AddDays(7);
else refToken.Expires = DateTime.UtcNow.AddDays(1);
_dataContext.ValidRefreshTokens.Add(refToken);
await _dataContext.SaveChangesAsync();
serviceResponse.Message = refToken.Token;
}
serviceResponse.Success = true;
return serviceResponse;
}
public async Task<ServiceResponse<string>> ValidateRefreshToken(string refreshToken)
{
var serviceResponse = new ServiceResponse<string>();
var dbRefresh = await _dataContext.ValidRefreshTokens.FirstOrDefaultAsync(x => x.Token == refreshToken);
if (dbRefresh != null)
{
if (dbRefresh.Expires < DateTime.UtcNow)
{
serviceResponse.Success = false;
serviceResponse.Message = "Refresh Token Expired.";
// Handle Expired Refresh Token
_dataContext.ValidRefreshTokens.Remove(dbRefresh);
await _dataContext.SaveChangesAsync();
return serviceResponse;
}
var user = await _dataContext.Users.FirstOrDefaultAsync(x => x.Id == dbRefresh.UserID);
if (user != null && dbRefresh.UserID == user.Id)
{
var token = await GenerateAccessTokenAndRefreshToken(user, false, false);
if (token != null)
{
serviceResponse.Success = true;
serviceResponse.Data = token.Data;
}
} else
{
serviceResponse.Success = false;
serviceResponse.Message = "Requesting User ID and the associated Refresh Token's User ID does not match.";
}
} else
{
serviceResponse.Success = false;
serviceResponse.Message = "Invalid Refresh Token.";
}
return serviceResponse;
}
public async Task<ServiceResponse<bool>> ValidateAccessToken(string accessToken)
{
var serviceResponse = new ServiceResponse<bool>();
var tokenHandler = new JwtSecurityTokenHandler();
var validationParams = GetValidationParams();
TokenValidationResult result = await tokenHandler.ValidateTokenAsync(accessToken, validationParams.Data);
if (result.IsValid)
{
serviceResponse.Success = true;
serviceResponse.Data = true;
return serviceResponse;
}
else
{
serviceResponse.Success = true;
serviceResponse.Data = false;
return serviceResponse;
}
}
public ServiceResponse<TokenValidationParameters> GetValidationParams()
{
var serviceResponse = new ServiceResponse<TokenValidationParameters>();
serviceResponse.Data = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = _configuration["Jwt:Issuer"],
ValidAudience = _configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]!))
};
return serviceResponse;
}
private long LongRandom(long min, long max, Random rnd)
{
long result = rnd.Next((int)(min >> 32), (int)(max >> 32));
result = result << 32;
result = result | (long)rnd.Next((int)min, (int)max);
return result;
}
}
}