From 24e0c22e0d98763bb1ffd6d69bb43d063390f211 Mon Sep 17 00:00:00 2001 From: Nguyen Quang Chien Date: Fri, 26 May 2023 19:04:15 +0700 Subject: [PATCH 1/2] update: refresh and login endpoint now delete db refresh tokens and refresh tokens are now persistent instead of fleeting --- .../Identity/IdentityService.cs | 56 +++++++++++++------ 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/src/Infrastructure/Identity/IdentityService.cs b/src/Infrastructure/Identity/IdentityService.cs index 58b7cf6e..cb38b55c 100644 --- a/src/Infrastructure/Identity/IdentityService.cs +++ b/src/Infrastructure/Identity/IdentityService.cs @@ -148,10 +148,19 @@ public async Task RefreshTokenAsync(string token, string r throw new AuthenticationException("This refresh token does not match this Jwt."); } - _context.RefreshTokens.Remove(storedRefreshToken); + var jweToken = CreateJweToken(user); + + storedRefreshToken.JwtId = jweToken.Id; + storedRefreshToken.ExpiryDateTime = + LocalDateTime.FromDateTime(DateTime.UtcNow.AddDays(_jweSettings.RefreshTokenLifetimeInDays)); + _context.RefreshTokens.Update(storedRefreshToken); await _context.SaveChangesAsync(); - return await GenerateAuthenticationResultForUserAsync(user); + return new AuthenticationResult() + { + Token = jweToken, + RefreshToken = _mapper.Map(storedRefreshToken) + }; } private ClaimsPrincipal? GetPrincipalFromToken(string token) @@ -196,6 +205,11 @@ public async Task RefreshTokenAsync(string token, string r throw new AuthenticationException("Username or password is invalid."); } + var existedRefreshTokens = _context.RefreshTokens.Where(x => x.User.Email!.Equals(user.Email)); + + _context.RemoveRange(existedRefreshTokens); + await _context.SaveChangesAsync(); + return (await GenerateAuthenticationResultForUserAsync(user), _mapper.Map(user)); } @@ -226,6 +240,28 @@ public async Task LogoutAsync(string token, string refreshToken) } private async Task GenerateAuthenticationResultForUserAsync(User user) + { + var token = CreateJweToken(user); + var utcNow = DateTime.UtcNow; + + var refreshToken = new RefreshToken() + { + JwtId = token.Id, + User = user, + CreationDateTime = LocalDateTime.FromDateTime(utcNow), + ExpiryDateTime = LocalDateTime.FromDateTime(utcNow.AddDays(_jweSettings.RefreshTokenLifetimeInDays)) + }; + + await _context.RefreshTokens.AddAsync(refreshToken); + await _context.SaveChangesAsync(); + return new() + { + Token = token, + RefreshToken = _mapper.Map(refreshToken) + }; + } + + private SecurityToken CreateJweToken(User user) { var utcNow = DateTime.UtcNow; var authClaims = new List @@ -252,20 +288,6 @@ private async Task GenerateAuthenticationResultForUserAsyn var token = handler.CreateToken(tokenDescriptor); - var refreshToken = new RefreshToken() - { - JwtId = token.Id, - User = user, - CreationDateTime = LocalDateTime.FromDateTime(utcNow), - ExpiryDateTime = LocalDateTime.FromDateTime(utcNow.AddDays(_jweSettings.RefreshTokenLifetimeInDays)) - }; - - await _context.RefreshTokens.AddAsync(refreshToken); - await _context.SaveChangesAsync(); - return new() - { - Token = token, - RefreshToken = _mapper.Map(refreshToken) - }; + return token; } } \ No newline at end of file From ca0f8a478950da6721c4e851af2ca890d4aae499 Mon Sep 17 00:00:00 2001 From: Nguyen Quang Chien Date: Sat, 27 May 2023 18:32:38 +0700 Subject: [PATCH 2/2] update: refresh token rotation --- src/Api/Controllers/AuthController.cs | 22 ++++++++++++++----- .../Identity/IdentityService.cs | 22 ++++--------------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/Api/Controllers/AuthController.cs b/src/Api/Controllers/AuthController.cs index da4bef58..da8d5ece 100644 --- a/src/Api/Controllers/AuthController.cs +++ b/src/Api/Controllers/AuthController.cs @@ -1,4 +1,5 @@ using System.IdentityModel.Tokens.Jwt; +using System.Security.Authentication; using Api.Controllers.Payload.Requests; using Api.Controllers.Payload.Responses; using Application.Common.Interfaces; @@ -70,12 +71,21 @@ public async Task Refresh() { var refreshToken = Request.Cookies[nameof(RefreshToken)]; var jweToken = Request.Cookies["JweToken"]; - - var authResult = await _identityService.RefreshTokenAsync(jweToken!, refreshToken!); - - SetRefreshToken(authResult.RefreshToken); - SetJweToken(authResult.Token, authResult.RefreshToken); - + + try + { + var authResult = await _identityService.RefreshTokenAsync(jweToken!, refreshToken!); + + SetRefreshToken(authResult.RefreshToken); + SetJweToken(authResult.Token, authResult.RefreshToken); + } + catch (AuthenticationException) + { + RemoveJweToken(); + RemoveRefreshToken(); + throw; + } + return Ok(); } diff --git a/src/Infrastructure/Identity/IdentityService.cs b/src/Infrastructure/Identity/IdentityService.cs index cb38b55c..15083d43 100644 --- a/src/Infrastructure/Identity/IdentityService.cs +++ b/src/Infrastructure/Identity/IdentityService.cs @@ -1,5 +1,6 @@ using System.Globalization; using System.IdentityModel.Tokens.Jwt; +using System.Reflection.Metadata.Ecma335; using System.Security.Authentication; using System.Security.Claims; using System.Security.Cryptography; @@ -148,19 +149,9 @@ public async Task RefreshTokenAsync(string token, string r throw new AuthenticationException("This refresh token does not match this Jwt."); } - var jweToken = CreateJweToken(user); + var result = await GenerateAuthenticationResultForUserAsync(user); - storedRefreshToken.JwtId = jweToken.Id; - storedRefreshToken.ExpiryDateTime = - LocalDateTime.FromDateTime(DateTime.UtcNow.AddDays(_jweSettings.RefreshTokenLifetimeInDays)); - _context.RefreshTokens.Update(storedRefreshToken); - await _context.SaveChangesAsync(); - - return new AuthenticationResult() - { - Token = jweToken, - RefreshToken = _mapper.Map(storedRefreshToken) - }; + return result; } private ClaimsPrincipal? GetPrincipalFromToken(string token) @@ -185,13 +176,8 @@ public async Task RefreshTokenAsync(string token, string r return principal; } - catch (SecurityTokenExpiredException ex) - { - return null; - } - catch (Exception exception) + catch { - Console.WriteLine(exception.StackTrace); return null; } }