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> GenerateAccessTokenAndRefreshToken(User user, bool generateRefToken, bool remember) { var serviceResponse = new ServiceResponse(); // Generate JWT Access Token List claims = new List() { new Claim(ClaimTypes.NameIdentifier, user.Id), new Claim(ClaimTypes.Name, user.Username), new Claim(ClaimTypes.Email, user.Email), new Claim(ClaimTypes.Role, user.Role), new Claim("TokenType", "access") }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.GetSection("Jwt:Key").Value ?? Environment.GetEnvironmentVariable("JWT_KEY")!)); 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.AddMonths(1); else refToken.Expires = DateTime.UtcNow.AddDays(3); _dataContext.ValidRefreshTokens.Add(refToken); var existingToken = _dataContext.ValidRefreshTokens.FirstOrDefault(e => e.UserID == refToken.UserID); if (existingToken != null) { refToken.Expires = existingToken.Expires; _dataContext.ValidRefreshTokens.Remove(existingToken); // we don't want multiple refresh tokens assigned to the user } await _dataContext.SaveChangesAsync(); serviceResponse.Message = refToken.Token; } serviceResponse.Success = true; return serviceResponse; } public ServiceResponse GenerateEmailConfirmationToken(User user) { var serviceResponse = new ServiceResponse(); // Generate JWT Access Token List claims = new List() { new Claim(ClaimTypes.NameIdentifier, user.Id), new Claim(ClaimTypes.Email, user.Email), new Claim("TokenType", "email-confirmation") }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.GetSection("Jwt:Key").Value ?? Environment.GetEnvironmentVariable("JWT_KEY")!)); 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(24), signingCredentials: creds ); var jwt = new JwtSecurityTokenHandler().WriteToken(token); serviceResponse.Success = true; serviceResponse.Data = jwt; return serviceResponse; } public ServiceResponse GeneratePasswordResetConfirmationToken(User user) { var serviceResponse = new ServiceResponse(); // Generate JWT Access Token List claims = new List() { new Claim(ClaimTypes.NameIdentifier, user.Id), new Claim(ClaimTypes.Email, user.Email), new Claim("TokenType", "password-reset") }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.GetSection("Jwt:Key").Value ?? Environment.GetEnvironmentVariable("JWT_KEY")!)); 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.Success = true; serviceResponse.Data = jwt; return serviceResponse; } public ServiceResponse GenerateClientPasswordResetConfirmationToken(User user) { var serviceResponse = new ServiceResponse(); // Generate JWT Access Token List claims = new List() { new Claim(ClaimTypes.NameIdentifier, user.Id), new Claim(ClaimTypes.Email, user.Email), new Claim("TokenType", "client-password-reset") }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.GetSection("Jwt:Key").Value ?? Environment.GetEnvironmentVariable("JWT_KEY")!)); 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.Success = true; serviceResponse.Data = jwt; return serviceResponse; } public async Task> ValidateRefreshToken(string refreshToken) { var serviceResponse = new ServiceResponse(); var dbRefresh = await _dataContext.ValidRefreshTokens.FirstOrDefaultAsync(x => x.Token == refreshToken); if (dbRefresh != null) { if (DateTime.Compare(DateTime.UtcNow, dbRefresh.Expires) > 0) { 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, true, false); if (token != null) { serviceResponse.Success = true; serviceResponse.Message = token.Message; 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> ValidateAccessToken(string accessToken) { var serviceResponse = new ServiceResponse(); 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 GetValidationParams() { var serviceResponse = new ServiceResponse(); var jwtIssuer = _configuration["Jwt:Issuer"]; var jwtAudience = _configuration["Jwt:Audience"]; var jwtKey = _configuration["Jwt:Key"] ?? Environment.GetEnvironmentVariable("JWT_KEY"); // check configuration for invalid jwt settings if (jwtIssuer is null || jwtAudience is null || jwtKey is null) throw new Exception("JWT Validation Parameters Are Not Set In appsettings.json. Please Set These Parameters."); serviceResponse.Data = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = jwtIssuer, ValidAudience = jwtAudience, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey)) }; 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; } } }