using BusinessERP.ConHelper; using BusinessERP.Data; using BusinessERP.JWTConfiguration; using BusinessERP.JWTConfiguration.DTOs.Requests; using BusinessERP.JWTConfiguration.DTOs.Responses; using BusinessERP.Models; using BusinessERP.Models.UserAccountViewModel; using BusinessERP.Services; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using System.Text; using System.Threading.Tasks; namespace BusinessERP.Controllers { [Route("api/[controller]")] // api/authManagement [ApiController] //[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public class AuthManagementController : ControllerBase { private readonly UserManager _userManager; //private readonly UserManager _userManager; private readonly JwtConfig _jwtConfig; private readonly TokenValidationParameters _tokenValidationParams; private readonly ApplicationDbContext _context; private readonly IEmailSender _emailSender; private readonly IAccount _iAccount; public AuthManagementController( UserManager userManager, IOptionsMonitor optionsMonitor, TokenValidationParameters tokenValidationParams, ApplicationDbContext context, IEmailSender emailSender, IAccount iAccount) { _userManager = userManager; _jwtConfig = optionsMonitor.CurrentValue; _tokenValidationParams = tokenValidationParams; _context = context; _emailSender = emailSender; _iAccount = iAccount; } [HttpGet] [Route("GetUserProfileList")] public async Task GetUserProfileList() { var items = await _context.UserProfile.ToListAsync(); return Ok(items); } [HttpPost] [Route("Register")] public async Task Register([FromBody] UserProfileViewModel _UserProfileViewModel) { if (ModelState.IsValid) { var _ApplicationUser = await _iAccount.CreateUserProfile(_UserProfileViewModel, HttpContext.User.Identity.Name); if (_ApplicationUser.Item2 == "Success") { var _DefaultIdentityOptions = await _context.DefaultIdentityOptions.FirstOrDefaultAsync(m => m.Id == 1); if (_DefaultIdentityOptions.SignInRequireConfirmedEmail) { var _ConfirmationToken = await _userManager.GenerateEmailConfirmationTokenAsync(_ApplicationUser.Item1); var callbackUrl = Url.EmailConfirmationLink(_ApplicationUser.Item1.Id, _ConfirmationToken, Request.Scheme); await _emailSender.SendEmailConfirmationAsync(_ApplicationUser.Item1.Email, callbackUrl); } var jwtToken = await GenerateJwtToken(_ApplicationUser.Item1); return Ok(jwtToken); } else { return GetBadRequest(_ApplicationUser.Item2); } } return GetBadRequest("Invalid payload"); } [HttpPost] [Route("Login")] public async Task Login([FromBody] UserLoginRequest user) { if (ModelState.IsValid) { var existingUser = await _userManager.FindByEmailAsync(user.Email); if (existingUser == null) { return GetBadRequest("Invalid login request"); } var isCorrect = await _userManager.CheckPasswordAsync(existingUser, user.Password); if (!isCorrect) { return GetBadRequest("Invalid login request"); } var jwtToken = await GenerateJwtToken(existingUser); return Ok(jwtToken); } return GetBadRequest("Invalid payload"); } [HttpPost] [Route("RefreshToken")] public async Task RefreshToken([FromBody] TokenRequest tokenRequest) { if (ModelState.IsValid) { var result = await VerifyAndGenerateToken(tokenRequest); if (result == null) { return GetBadRequest("Invalid tokens"); } return Ok(result); } return GetBadRequest("Invalid payload"); } private async Task GenerateJwtToken(ApplicationUser _ApplicationUser) { var jwtTokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes(_jwtConfig.Secret); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(new[] { new Claim("Id", _ApplicationUser.Id), new Claim(JwtRegisteredClaimNames.Email, _ApplicationUser.Email), new Claim(JwtRegisteredClaimNames.Sub, _ApplicationUser.Email), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) }), Expires = DateTime.UtcNow.AddSeconds(30), // 5-10 SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var token = jwtTokenHandler.CreateToken(tokenDescriptor); var jwtToken = jwtTokenHandler.WriteToken(token); var refreshToken = new RefreshToken() { JwtId = token.Id, IsUsed = false, IsRevorked = false, UserId = _ApplicationUser.Id, AddedDate = DateTime.UtcNow, ExpiryDate = DateTime.UtcNow.AddMonths(6), Token = RandomString(35) + Guid.NewGuid(), CreatedDate = DateTime.UtcNow, ModifiedDate = DateTime.UtcNow, }; await _context.RefreshToken.AddAsync(refreshToken); await _context.SaveChangesAsync(); return new AuthResult() { Token = jwtToken, Success = true, RefreshToken = refreshToken.Token }; } private async Task VerifyAndGenerateToken(TokenRequest tokenRequest) { var jwtTokenHandler = new JwtSecurityTokenHandler(); try { // Validation 1 - Validation JWT token format var tokenInVerification = jwtTokenHandler.ValidateToken(tokenRequest.Token, _tokenValidationParams, out var validatedToken); // Validation 2 - Validate encryption alg if (validatedToken is JwtSecurityToken jwtSecurityToken) { var result = jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase); if (result == false) { return null; } } // Validation 3 - validate expiry date var utcExpiryDate = long.Parse(tokenInVerification.Claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Exp).Value); var expiryDate = UnixTimeStampToDateTime(utcExpiryDate); if (expiryDate > DateTime.UtcNow) { return new AuthResult() { Success = false, Errors = new List() { "Token has not yet expired" } }; } // validation 4 - validate existence of the token var storedToken = await _context.RefreshToken.FirstOrDefaultAsync(x => x.Token == tokenRequest.RefreshToken); if (storedToken == null) { return new AuthResult() { Success = false, Errors = new List() { "Token does not exist" } }; } // Validation 5 - validate if used if (storedToken.IsUsed) { return new AuthResult() { Success = false, Errors = new List() { "Token has been used" } }; } // Validation 6 - validate if revoked if (storedToken.IsRevorked) { return new AuthResult() { Success = false, Errors = new List() { "Token has been revoked" } }; } // Validation 7 - validate the id var jti = tokenInVerification.Claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Jti).Value; if (storedToken.JwtId != jti) { return new AuthResult() { Success = false, Errors = new List() { "Token doesn't match" } }; } // update current token storedToken.IsUsed = true; storedToken.ModifiedDate = DateTime.UtcNow; _context.RefreshToken.Update(storedToken); await _context.SaveChangesAsync(); // Generate a new token var dbUser = await _userManager.FindByIdAsync(storedToken.UserId); return await GenerateJwtToken(dbUser); } catch (Exception ex) { if (ex.Message.Contains("Lifetime validation failed. The token is expired.")) { return new AuthResult() { Success = false, Errors = new List() { "Token has expired please re-login" } }; } else { return new AuthResult() { Success = false, Errors = new List() { "Something went wrong." } }; } } } private DateTime UnixTimeStampToDateTime(long unixTimeStamp) { var dateTimeVal = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); dateTimeVal = dateTimeVal.AddSeconds(unixTimeStamp).ToUniversalTime(); return dateTimeVal; } private string RandomString(int length) { var random = new Random(); var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; return new string(Enumerable.Repeat(chars, length) .Select(x => x[random.Next(x.Length)]).ToArray()); } private IActionResult GetBadRequest(string ErrorsMessage) { return BadRequest(new RegistrationResponse() { Errors = new List() { ErrorsMessage }, Success = false }); } } }