Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

AuthenticationService.cs 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. using Microsoft.AspNetCore.WebUtilities;
  2. using Microsoft.Extensions.Logging;
  3. using System.Net;
  4. namespace Diligent.WebAPI.Business.Services
  5. {
  6. public class AuthenticationService : IAuthenticationService
  7. {
  8. private readonly AuthorizationSettings _authSettings;
  9. private readonly FrontEndSettings _frontEndSettings;
  10. private readonly UserManager<User> _userManager;
  11. private readonly DatabaseContext _databaseContext;
  12. private readonly IEmailer _emailer;
  13. private readonly ILogger<AuthenticationService> _logger;
  14. private readonly IHttpClientService _httpClient;
  15. public AuthenticationService(IOptions<AuthorizationSettings> authSettings,
  16. IOptions<FrontEndSettings> frontEndSettings,
  17. UserManager<User> userManager,
  18. DatabaseContext databaseContext,
  19. IEmailer emailer,
  20. ILogger<AuthenticationService> logger,
  21. IHttpClientService httpClient)
  22. {
  23. _authSettings = authSettings.Value;
  24. _frontEndSettings = frontEndSettings.Value;
  25. _userManager = userManager;
  26. _databaseContext = databaseContext;
  27. _httpClient = httpClient;
  28. _emailer = emailer;
  29. _logger = logger;
  30. }
  31. public async Task<ServiceResponseDTO<AuthenticateResponseDto>> Authenticate(AuthenticateRequestDto model)
  32. {
  33. var user = await _userManager.FindByNameAsync(model.Username);
  34. // return null if user not found
  35. if (user == null)
  36. {
  37. return new ServiceResponseDTO<AuthenticateResponseDto>
  38. {
  39. IsError = true,
  40. ErrorMessage = "Username is not valid"
  41. };
  42. }
  43. var result = await _userManager.CheckPasswordAsync(user, model.Password);
  44. // password is not correct
  45. if (!result)
  46. {
  47. await _userManager.AccessFailedAsync(user);
  48. return new ServiceResponseDTO<AuthenticateResponseDto>
  49. {
  50. IsError = true,
  51. ErrorMessage = "Password is not correct"
  52. };
  53. }
  54. return await GenerateToken(user);
  55. }
  56. public async Task<ServiceResponseDTO<AuthenticateResponseDto>> Authenticate(GoogleApiModel model)
  57. {
  58. if (!(await _httpClient.IsTokenValid(model.Token)))
  59. {
  60. return new ServiceResponseDTO<AuthenticateResponseDto>
  61. {
  62. IsError = true,
  63. ErrorMessage = "Invalid Google Api Token"
  64. };
  65. }
  66. var user = await _userManager.FindByEmailAsync(model.User.email);
  67. // return null if user not found
  68. if (user == null)
  69. {
  70. return new ServiceResponseDTO<AuthenticateResponseDto>
  71. {
  72. IsError = true,
  73. ErrorMessage = $"User with email {model.User.email} does not exist in database"
  74. };
  75. }
  76. return await GenerateToken(user);
  77. }
  78. private async Task<ServiceResponseDTO<AuthenticateResponseDto>> GenerateToken(User user)
  79. {
  80. var isLocked = await _userManager.IsLockedOutAsync(user);
  81. if (isLocked)
  82. return new ServiceResponseDTO<AuthenticateResponseDto>
  83. {
  84. IsError = true,
  85. ErrorMessage = "The account is locked out"
  86. };
  87. // authentication successful so generate jwt token
  88. var token = await GenerateJwtToken(user, true);
  89. var data = new AuthenticateResponseDto
  90. {
  91. Id = user.Id,
  92. Username = user.UserName,
  93. FirstName = user.FirstName,
  94. LastName = user.LastName,
  95. Token = token,
  96. RefreshToken = token
  97. };
  98. return new ServiceResponseDTO<AuthenticateResponseDto>
  99. {
  100. Data = data
  101. };
  102. }
  103. private async Task<string> GenerateJwtToken(User user, bool authenticate = false)
  104. {
  105. // generate token that is valid for 7 days
  106. var tokenHandler = new JwtSecurityTokenHandler();
  107. var key = Encoding.ASCII.GetBytes(_authSettings.Secret);
  108. var tokenDescriptor = new SecurityTokenDescriptor
  109. {
  110. Subject = new ClaimsIdentity(new[] {
  111. new Claim(JwtRegisteredClaimNames.Jti, user.Id.ToString()),
  112. new Claim("id", user.Id.ToString())
  113. }),
  114. Expires = DateTime.UtcNow.AddMinutes(_authSettings.JwtExpiredTime),
  115. SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
  116. };
  117. var token = tokenHandler.CreateToken(tokenDescriptor);
  118. var writedToken = tokenHandler.WriteToken(token);
  119. var refreshToken = new RefreshToken
  120. {
  121. Token = writedToken,
  122. JwtId = user.Id.ToString(),
  123. UserId = user.Id,
  124. User = user,
  125. CreationDate = DateTime.UtcNow,
  126. ExpiryDate = DateTime.UtcNow.AddMinutes(_authSettings.JwtRefreshExpiredTime)
  127. };
  128. var existRefreshToken = await _databaseContext.RefreshTokens.Where(x => x.UserId == user.Id).FirstOrDefaultAsync();
  129. if (existRefreshToken != null)
  130. {
  131. existRefreshToken.Token = writedToken;
  132. existRefreshToken.JwtId = token.Id;
  133. existRefreshToken.CreationDate = DateTime.UtcNow;
  134. existRefreshToken.ExpiryDate = DateTime.UtcNow.AddMinutes(_authSettings.JwtRefreshExpiredTime);
  135. if (authenticate)
  136. {
  137. existRefreshToken.Used = false;
  138. existRefreshToken.Invalidated = false;
  139. }
  140. _databaseContext.RefreshTokens.Update(existRefreshToken);
  141. await UpdateRefreshToken(existRefreshToken);
  142. }
  143. else
  144. {
  145. await _databaseContext.RefreshTokens.AddAsync(refreshToken);
  146. }
  147. await _databaseContext.SaveChangesAsync();
  148. return writedToken;
  149. }
  150. public async Task<RefreshTokenResultDto> RefreshTokenAsync(RefreshTokenRequestDto model)
  151. {
  152. var validatedToken = GetPrincipalFromToken(model.Token, false);
  153. if (validatedToken == null)
  154. {
  155. return new RefreshTokenResultDto { Error = "Invalid token" };
  156. }
  157. var jti = validatedToken.Claims.Single(x => x.Type == JwtRegisteredClaimNames.Jti).Value;
  158. var storedRefreshToken = await _databaseContext.RefreshTokens.SingleOrDefaultAsync(x => x.JwtId == jti);
  159. if (storedRefreshToken == null)
  160. {
  161. return new RefreshTokenResultDto { Error = "This refresh token does not exist" };
  162. }
  163. var userk = await _databaseContext.Users.Where(u => u.Id == storedRefreshToken.UserId).FirstOrDefaultAsync();
  164. if (userk == null)
  165. {
  166. return new RefreshTokenResultDto { Error = "There is no user which is associated with refresh token" };
  167. }
  168. var expiryDateUnix = long.Parse(validatedToken.Claims.Single(x => x.Type == JwtRegisteredClaimNames.Exp).Value);
  169. var expiryDateTimeUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)
  170. .AddMinutes(expiryDateUnix);
  171. if (expiryDateTimeUtc < DateTime.UtcNow)
  172. {
  173. return new RefreshTokenResultDto
  174. {
  175. Data = new AuthenticateResponseDto
  176. {
  177. Id = userk.Id,
  178. FirstName = userk.FirstName,
  179. LastName = userk.LastName,
  180. Username = userk.UserName,
  181. Token = model.Token,
  182. RefreshToken = model.RefreshToken
  183. }
  184. };
  185. }
  186. if (DateTime.UtcNow > storedRefreshToken.ExpiryDate)
  187. {
  188. return new RefreshTokenResultDto { Error = "This refresh token has expired" };
  189. }
  190. if (storedRefreshToken.Invalidated)
  191. {
  192. return new RefreshTokenResultDto { Error = "This refresh token has been invalidated" };
  193. }
  194. if (storedRefreshToken.JwtId != jti)
  195. {
  196. return new RefreshTokenResultDto { Error = "This refresh token does not match this JWT" };
  197. }
  198. storedRefreshToken.ExpiryDate = DateTime.UtcNow.AddMinutes(_authSettings.JwtRefreshExpiredTime);
  199. await _databaseContext.SaveChangesAsync();
  200. var user = await _userManager.FindByIdAsync(validatedToken.Claims.Single(x => x.Type == "id").Value);
  201. var token = await GenerateJwtToken(user);
  202. return new RefreshTokenResultDto
  203. {
  204. Data = new AuthenticateResponseDto
  205. {
  206. Id = userk.Id,
  207. FirstName = userk.FirstName,
  208. LastName = userk.LastName,
  209. Username = userk.UserName,
  210. Token = model.Token,
  211. RefreshToken = model.RefreshToken
  212. }
  213. };
  214. }
  215. public async Task<ServiceResponseDTO<string>> DeleteRefreshToken(int userId)
  216. {
  217. var refreshToken = await _databaseContext.RefreshTokens.Where(r => r.UserId == userId).FirstOrDefaultAsync();
  218. if (refreshToken is null)
  219. return new ServiceResponseDTO<string>
  220. {
  221. IsError = true,
  222. ErrorMessage = "There is no refresh token for user"
  223. };
  224. _databaseContext.RefreshTokens.Remove(refreshToken);
  225. var result = await _databaseContext.SaveChangesAsync() > 0;
  226. if (!result)
  227. return new ServiceResponseDTO<string>
  228. {
  229. IsError = true,
  230. ErrorMessage = "Problem with saving changes into database"
  231. };
  232. return new ServiceResponseDTO<string>
  233. {
  234. Data = null
  235. };
  236. }
  237. private ClaimsPrincipal? GetPrincipalFromToken(string token, bool validateLifetime)
  238. {
  239. var tokenHandler = new JwtSecurityTokenHandler();
  240. var key = Encoding.ASCII.GetBytes(_authSettings.Secret);
  241. var tokenValidationParameters = new TokenValidationParameters
  242. {
  243. ValidateIssuerSigningKey = true,
  244. IssuerSigningKey = new SymmetricSecurityKey(key),
  245. ValidateIssuer = false,
  246. ValidateAudience = false,
  247. RequireExpirationTime = false,
  248. ValidateLifetime = validateLifetime,
  249. // set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
  250. //ClockSkew = TimeSpan.Zero
  251. };
  252. try
  253. {
  254. var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out var validatedToken);
  255. if (!IsJwtWithValidSecurityAlgorithm(validatedToken))
  256. {
  257. return null;
  258. }
  259. return principal;
  260. }
  261. catch (Exception)
  262. {
  263. return null;
  264. }
  265. }
  266. private static bool IsJwtWithValidSecurityAlgorithm(SecurityToken validatedToken)
  267. {
  268. return (validatedToken is JwtSecurityToken jwtSecurityToken) &&
  269. jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256,
  270. StringComparison.InvariantCultureIgnoreCase);
  271. }
  272. public async Task<RefreshToken?> GetRefreshTokenByUserId(int userId)
  273. {
  274. return await _databaseContext.RefreshTokens.Where(x => x.UserId == userId).FirstOrDefaultAsync();
  275. }
  276. public async Task UpdateRefreshToken(RefreshToken refreshToken)
  277. {
  278. _databaseContext.RefreshTokens.Update(refreshToken);
  279. await _databaseContext.SaveChangesAsync();
  280. }
  281. public async Task<ServiceResponseDTO<object>> GetEmailConfirmationUrlAsync(string email)
  282. {
  283. var user = await _userManager.FindByEmailAsync(email);
  284. if (user == null)
  285. {
  286. return new ServiceResponseDTO<object>
  287. {
  288. IsError = true,
  289. ErrorMessage = "Email did not find."
  290. };
  291. }
  292. var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
  293. token = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(token));
  294. await _emailer.SendEmailAndWriteToDbAsync(email, "Reset password", HTMLHelper.RenderForgotPasswordPage($"{_frontEndSettings.BaseUrl}/reset-password?token={token}&email={email}"), isHtml: true);
  295. user.PasswordResetToken = token;
  296. await _databaseContext.SaveChangesAsync();
  297. return new ServiceResponseDTO<object>
  298. {
  299. Data = new { code = token, email = email }
  300. };
  301. }
  302. public async Task<ServiceResponseDTO<object>> PasswordResetAsync(string email, string code, string password)
  303. {
  304. var user = await _userManager.FindByEmailAsync(email);
  305. if (user == null)
  306. {
  307. return new ServiceResponseDTO<object>
  308. {
  309. IsError = true,
  310. ErrorMessage = "Email did not find."
  311. };
  312. }
  313. // FOR SOME REASON USERMANAGER.RESETPASSWORDASYNC returns InvalidToken. In future change this
  314. //var passwordResetToken = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
  315. //IdentityResult resetResult = await _userManager.ResetPasswordAsync(user, passwordResetToken, password);
  316. //if (resetResult.Succeeded)
  317. await _userManager.RemovePasswordAsync(user);
  318. await _userManager.AddPasswordAsync(user, password);
  319. if (user.PasswordResetToken == code)
  320. {
  321. if (await _userManager.IsLockedOutAsync(user))
  322. {
  323. await _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow);
  324. }
  325. return new ServiceResponseDTO<object> { Data = true };
  326. }
  327. //var errors = resetResult.Errors.Select(x => x.Description);
  328. return new ServiceResponseDTO<object>
  329. {
  330. IsError = true,
  331. ErrorMessage = "Invalid reset password token"
  332. };
  333. }
  334. }
  335. }