namespace Diligent.WebAPI.Business.Services { public class UserService : IUserService { private readonly AuthorizationSettings _authSettings; private readonly UserManager _userManager; private readonly IMapper _mapper; private readonly DatabaseContext _databaseContext; public UserService(IOptions authSettings, UserManager userManager, IMapper mapper, DatabaseContext databaseContext) { _authSettings = authSettings.Value; _userManager = userManager; _mapper = mapper; _databaseContext = databaseContext; } public async Task> GetAll() => await _userManager.Users.ToListAsync(); public async Task GetById(int id) => await _userManager.FindByIdAsync(id.ToString()); public async Task CreateUser(CreateUserRequestDto model) { var user = _mapper.Map(model); await _userManager.CreateAsync(user, model.Password); } public async Task> Authenticate(AuthenticateRequestDto model) { var user = await _userManager.FindByNameAsync(model.Username); // return null if user not found if (user == null) { return new ServiceResponseDTO { IsError = true, ErrorMessage = "Username is not valid" }; } var isLocked = await _userManager.IsLockedOutAsync(user); if (isLocked) return new ServiceResponseDTO { IsError = true, ErrorMessage = "The account is locked out" }; var result = await _userManager.CheckPasswordAsync(user, model.Password); // password is not correct if (!result) { await _userManager.AccessFailedAsync(user); isLocked = await _userManager.IsLockedOutAsync(user); if(isLocked) return new ServiceResponseDTO { IsError = true, ErrorMessage = "The account is locked out" }; return new ServiceResponseDTO { IsError = true, ErrorMessage = "Password is not correct" }; } // authentication successful so generate jwt token var token = await GenerateJwtToken(user, true); var data = new AuthenticateResponseDto { Id = user.Id, Username = user.UserName, FirstName = user.FirstName, LastName = user.LastName, Token = token, RefreshToken = token }; return new ServiceResponseDTO { Data = data }; } private async Task GenerateJwtToken(User user, bool authenticate = false) { // generate token that is valid for 7 days var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes(_authSettings.Secret); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(new[] { new Claim(JwtRegisteredClaimNames.Jti, user.Id.ToString()), new Claim("id", user.Id.ToString()) }), Expires = DateTime.UtcNow.AddMinutes(_authSettings.JwtExpiredTime), SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var token = tokenHandler.CreateToken(tokenDescriptor); var writedToken = tokenHandler.WriteToken(token); var refreshToken = new RefreshToken { Token = writedToken, JwtId = user.Id.ToString(), UserId = user.Id, User = user, CreationDate = DateTime.UtcNow, ExpiryDate = DateTime.UtcNow.AddMinutes(_authSettings.JwtRefreshExpiredTime) }; var existRefreshToken = await _databaseContext.RefreshTokens.Where(x => x.UserId == user.Id).FirstOrDefaultAsync(); if(existRefreshToken != null) { existRefreshToken.Token = writedToken; existRefreshToken.JwtId = token.Id; existRefreshToken.CreationDate = DateTime.UtcNow; existRefreshToken.ExpiryDate = DateTime.UtcNow.AddMinutes(_authSettings.JwtRefreshExpiredTime); if (authenticate) { existRefreshToken.Used = false; existRefreshToken.Invalidated = false; } _databaseContext.RefreshTokens.Update(existRefreshToken); await UpdateRefreshToken(existRefreshToken); } else { await _databaseContext.RefreshTokens.AddAsync(refreshToken); } await _databaseContext.SaveChangesAsync(); return writedToken; } public async Task RefreshTokenAsync(RefreshTokenRequestDto model) { var validatedToken = GetPrincipalFromToken(model.Token); if (validatedToken == null) { return new RefreshTokenResultDto { Error = "Invalid token" }; } var jti = validatedToken.Claims.Single(x => x.Type == JwtRegisteredClaimNames.Jti).Value; var storedRefreshToken = await _databaseContext.RefreshTokens.SingleOrDefaultAsync(x => x.JwtId == jti); if (storedRefreshToken == null) { return new RefreshTokenResultDto { Error = "This refresh token does not exist" }; } var userk = await _databaseContext.Users.Where(u => u.Id == storedRefreshToken.UserId).FirstOrDefaultAsync(); if(userk == null) { return new RefreshTokenResultDto { Error = "There is no user which is associated with refresh token" }; } var expiryDateUnix = long.Parse(validatedToken.Claims.Single(x => x.Type == JwtRegisteredClaimNames.Exp).Value); var expiryDateTimeUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc) .AddMinutes(expiryDateUnix); if (expiryDateTimeUtc < DateTime.UtcNow) { return new RefreshTokenResultDto { Data = new AuthenticateResponseDto { Id = userk.Id, FirstName = userk.FirstName, LastName = userk.LastName, Username = userk.UserName, Token = model.Token, RefreshToken = model.RefreshToken } }; } if (DateTime.UtcNow > storedRefreshToken.ExpiryDate) { return new RefreshTokenResultDto { Error = "This refresh token has expired" }; } if (storedRefreshToken.Invalidated) { return new RefreshTokenResultDto { Error = "This refresh token has been invalidated" }; } if (storedRefreshToken.JwtId != jti) { return new RefreshTokenResultDto { Error = "This refresh token does not match this JWT" }; } storedRefreshToken.ExpiryDate = DateTime.UtcNow.AddMinutes(_authSettings.JwtRefreshExpiredTime); await _databaseContext.SaveChangesAsync(); var user = await _userManager.FindByIdAsync(validatedToken.Claims.Single(x => x.Type == "id").Value); var token = await GenerateJwtToken(user); return new RefreshTokenResultDto { Data = new AuthenticateResponseDto { Id = userk.Id, FirstName = userk.FirstName, LastName = userk.LastName, Username = userk.UserName, Token = model.Token, RefreshToken = model.RefreshToken } }; } public async Task> DeleteRefreshToken(int userId) { var refreshToken = await _databaseContext.RefreshTokens.Where(r => r.UserId == userId).FirstOrDefaultAsync(); if (refreshToken is null) return new ServiceResponseDTO { IsError = true, ErrorMessage = "There is no refresh token for user" }; _databaseContext.RefreshTokens.Remove(refreshToken); var result = await _databaseContext.SaveChangesAsync() > 0; if (!result) return new ServiceResponseDTO { IsError = true, ErrorMessage = "Problem with saving changes into database" }; return new ServiceResponseDTO { Data = null }; } private ClaimsPrincipal? GetPrincipalFromToken(string token) { var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes(_authSettings.Secret); var tokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(key), ValidateIssuer = false, ValidateAudience = false, RequireExpirationTime = false, ValidateLifetime = true, // set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later) //ClockSkew = TimeSpan.Zero }; try { var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out var validatedToken); if (!IsJwtWithValidSecurityAlgorithm(validatedToken)) { return null; } return principal; } catch (Exception) { return null; } } private static bool IsJwtWithValidSecurityAlgorithm(SecurityToken validatedToken) { return (validatedToken is JwtSecurityToken jwtSecurityToken) && jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase); } public async Task GetRefreshTokenByUserId(int userId) { return await _databaseContext.RefreshTokens.Where(x => x.UserId == userId).FirstOrDefaultAsync(); } public async Task UpdateRefreshToken(RefreshToken refreshToken) { _databaseContext.RefreshTokens.Update(refreshToken); await _databaseContext.SaveChangesAsync(); } } }