using LANCommander.Data.Models; using LANCommander.Models; using LANCommander.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Security.Cryptography; using System.Text; namespace LANCommander.Controllers.Api { public class TokenModel { public string AccessToken { get; set; } public string RefreshToken { get; set; } } public class LoginModel { public string UserName { get; set; } public string Password { get; set; } } [Route("api/[controller]")] [ApiController] public class AuthController : ControllerBase { private readonly UserManager UserManager; private readonly RoleManager RoleManager; private readonly LANCommanderSettings Settings; public AuthController(UserManager userManager, RoleManager roleManager) { UserManager = userManager; RoleManager = roleManager; Settings = SettingService.GetSettings(); } [HttpPost] public async Task Login([FromBody] LoginModel model) { var user = await UserManager.FindByNameAsync(model.UserName); if (user != null && await UserManager.CheckPasswordAsync(user, model.Password)) { var userRoles = await UserManager.GetRolesAsync(user); var authClaims = new List { new Claim(ClaimTypes.Name, user.UserName), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) }; foreach (var userRole in userRoles) { authClaims.Add(new Claim(ClaimTypes.Role, userRole)); } var token = GetToken(authClaims); var refreshToken = GenerateRefreshToken(); user.RefreshToken = refreshToken; user.RefreshTokenExpiration = DateTime.Now.AddDays(Settings.TokenLifetime); await UserManager.UpdateAsync(user); return Ok(new { AccessToken = new JwtSecurityTokenHandler().WriteToken(token), RefreshToken = refreshToken, Expiration = token.ValidTo }); } return Unauthorized(); } [HttpPost("Validate")] [Authorize(AuthenticationSchemes = "Bearer")] public IActionResult Validate() { if (User != null && User.Identity != null && User.Identity.IsAuthenticated) return Ok(); else return Unauthorized(); } [HttpPost("Refresh")] public async Task Refresh(TokenModel token) { if (token == null) { return BadRequest("Invalid client request"); } var principal = GetPrincipalFromExpiredToken(token.AccessToken); if (principal == null) { return BadRequest("Invalid access token or refresh token"); } var user = await UserManager.FindByNameAsync(principal.Identity.Name); if (user == null || user.RefreshToken != token.RefreshToken || user.RefreshTokenExpiration <= DateTime.Now) { return BadRequest("Invalid access token or refresh token"); } var newAccessToken = GetToken(principal.Claims.ToList()); var newRefreshToken = GenerateRefreshToken(); user.RefreshToken = newRefreshToken; await UserManager.UpdateAsync(user); return Ok(new { AccessToken = new JwtSecurityTokenHandler().WriteToken(newAccessToken), RefreshToken = newRefreshToken, Expiration = newAccessToken.ValidTo }); } private JwtSecurityToken GetToken(List authClaims) { var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Settings.TokenSecret)); var token = new JwtSecurityToken( expires: DateTime.Now.AddDays(Settings.TokenLifetime), claims: authClaims, signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256) ); return token; } private static string GenerateRefreshToken() { var randomNumber = new byte[64]; using (var rng = RandomNumberGenerator.Create()) { rng.GetBytes(randomNumber); return Convert.ToBase64String(randomNumber); } } private ClaimsPrincipal? GetPrincipalFromExpiredToken(string? token) { var tokenValidationParameters = new TokenValidationParameters { ValidateAudience = false, ValidateIssuer = false, ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Settings.TokenSecret)), ValidateLifetime = false }; var tokenHandler = new JwtSecurityTokenHandler(); var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out SecurityToken securityToken); if (securityToken is not JwtSecurityToken jwtSecurityToken || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase)) throw new SecurityTokenException("Invalid token"); return principal; } } }