Skip to content

Commit 7e1f5c4

Browse files
author
TL\anwarahm
committed
refresh token endpoint created
1 parent 76b80bb commit 7e1f5c4

19 files changed

+942
-34
lines changed

src/Common/General/SiteSettings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@ public class JwtSettings
2424
public string Audience { get; set; }
2525
public int NotBeforeMinutes { get; set; }
2626
public int ExpirationMinutes { get; set; }
27+
public int RefreshTokenValidityInDays { get; set; }
2728
}
2829
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using MediatR;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
namespace CleanTemplate.Application.Users.Command.RefreshToken
9+
{
10+
public class RefreshTokenCommand : IRequest<RefreshTokenResponse>
11+
{
12+
public string RefreshToken { get; set; }
13+
public string AccessToken { get; set; }
14+
}
15+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace CleanTemplate.Application.Users.Command.RefreshToken
8+
{
9+
public class RefreshTokenResponse
10+
{
11+
public string RefreshToken { get; set; }
12+
public string AccessToken { get; set; }
13+
}
14+
}

src/Core/Domain/Entities/Users/User.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ public User()
2121
public bool IsActive { get; set; }
2222

2323
public DateTimeOffset? LastLoginDate { get; set; }
24+
public string? RefreshToken { get; set; }
25+
public DateTime? RefreshTokenExpiryTime { get; set; }
2426
}
2527

2628
public enum GenderType

src/Infrastructure/Persistance/CommandHandlers/Users/LoginCommandHandler.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ public async Task<LoginResponse> Handle(LoginCommand request, CancellationToken
3737
throw new CleanArchAppException("username or password is incorrect");
3838

3939
var jwt = await _jwtService.GenerateAsync(user);
40-
40+
user.RefreshToken = jwt.refresh_token;
41+
user.RefreshTokenExpiryTime = DateTime.Now.AddDays(jwt.refreshToken_expiresIn);
42+
await _userManager.UpdateAsync(user);
4143
return new LoginResponse
4244
{
4345
accessToken = jwt.access_token,
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using CleanTemplate.Application.Users.Command.RefreshToken;
2+
using CleanTemplate.Common;
3+
using CleanTemplate.Domain.Entities.Users;
4+
using CleanTemplate.Domain.IRepositories;
5+
using CleanTemplate.Persistance.Jwt;
6+
using MediatR;
7+
using Microsoft.AspNetCore.Identity;
8+
using Microsoft.IdentityModel.Tokens;
9+
using System;
10+
using System.Collections.Generic;
11+
using System.IdentityModel.Tokens.Jwt;
12+
using System.Linq;
13+
using System.Text;
14+
using System.Threading;
15+
using System.Threading.Tasks;
16+
17+
namespace CleanTemplate.Persistance.CommandHandlers.Users
18+
{
19+
public class RefreshTokenCommandHandler : IRequestHandler<RefreshTokenCommand, RefreshTokenResponse>
20+
{
21+
private readonly IUserRepository _userRepository;
22+
private readonly IJwtService _jwtService;
23+
24+
public RefreshTokenCommandHandler(IUserRepository userRepository,
25+
IJwtService jwtService)
26+
{
27+
_userRepository = userRepository ?? throw new ArgumentNullException(nameof(userRepository));
28+
_jwtService = jwtService ?? throw new ArgumentNullException(nameof(jwtService));
29+
}
30+
public async Task<RefreshTokenResponse> Handle(RefreshTokenCommand request, CancellationToken cancellationToken)
31+
{
32+
var userId = _jwtService.ValidateJwtAccessTokenAsync(request.AccessToken);
33+
if (userId == null)
34+
throw new CleanArchAppException("AccessToken is not valid");
35+
36+
var user = await _userRepository.GetByIdAsync(cancellationToken, userId);
37+
if (user.RefreshToken != request.RefreshToken)
38+
throw new CleanArchAppException("RefreshToken is not valid");
39+
40+
var jwt = await _jwtService.GenerateAsync(user);
41+
user.RefreshToken = jwt.refresh_token;
42+
user.RefreshTokenExpiryTime = DateTime.Now.AddDays(jwt.refreshToken_expiresIn);
43+
await _userRepository.UpdateAsync(user, cancellationToken);
44+
return new RefreshTokenResponse
45+
{
46+
AccessToken = jwt.access_token,
47+
RefreshToken = jwt.refresh_token
48+
};
49+
}
50+
}
51+
}

src/Infrastructure/Persistance/Jwt/AccessToken.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,21 @@ public class AccessToken
99
public string refresh_token { get; set; }
1010
public string token_type { get; set; }
1111
public int expires_in { get; set; }
12+
public int refreshToken_expiresIn { get; set; }
1213

1314
public AccessToken(JwtSecurityToken securityToken)
1415
{
1516
access_token = new JwtSecurityTokenHandler().WriteToken(securityToken);
1617
token_type = "Bearer";
1718
expires_in = (int)(securityToken.ValidTo - DateTime.UtcNow).TotalSeconds;
1819
}
20+
public AccessToken(JwtSecurityToken securityToken,string refreshToken, int refreshTokenExpiresIn)
21+
{
22+
access_token = new JwtSecurityTokenHandler().WriteToken(securityToken);
23+
token_type = "Bearer";
24+
expires_in = (int)(securityToken.ValidTo - DateTime.UtcNow).TotalSeconds;
25+
refresh_token = refreshToken;
26+
refreshToken_expiresIn = refreshTokenExpiresIn;
27+
}
1928
}
2029
}

src/Infrastructure/Persistance/Jwt/IJwtService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ namespace CleanTemplate.Persistance.Jwt
66
public interface IJwtService
77
{
88
Task<AccessToken> GenerateAsync(User user);
9+
int? ValidateJwtAccessTokenAsync(string token);
910
}
1011
}

src/Infrastructure/Persistance/Jwt/JwtService.cs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
using System;
88
using System.Collections.Generic;
99
using System.IdentityModel.Tokens.Jwt;
10+
using System.Linq;
1011
using System.Security.Claims;
12+
using System.Security.Cryptography;
1113
using System.Text;
1214
using System.Threading.Tasks;
1315

@@ -51,7 +53,38 @@ public async Task<AccessToken> GenerateAsync(User user)
5153

5254
var securityToken = tokenHandler.CreateJwtSecurityToken(descriptor);
5355

54-
return new AccessToken(securityToken);
56+
return new AccessToken(securityToken: securityToken,
57+
refreshToken: GenerateRefreshToken(),
58+
refreshTokenExpiresIn: _siteSetting.JwtSettings.RefreshTokenValidityInDays);
59+
}
60+
61+
public int? ValidateJwtAccessTokenAsync(string token)
62+
{
63+
var secretKey = Encoding.UTF8.GetBytes(_siteSetting.JwtSettings.SecretKey); // longer that 16 character
64+
var encryptionkey = Encoding.UTF8.GetBytes(_siteSetting.JwtSettings.EncryptKey); //must be 16 character
65+
66+
var tokenHandler = new JwtSecurityTokenHandler();
67+
try
68+
{
69+
tokenHandler.ValidateToken(token, new TokenValidationParameters
70+
{
71+
ValidateIssuerSigningKey = true,
72+
IssuerSigningKey = new SymmetricSecurityKey(secretKey),
73+
TokenDecryptionKey = new SymmetricSecurityKey(encryptionkey),
74+
ValidateIssuer = false,
75+
ValidateAudience = false,
76+
// set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
77+
ClockSkew = TimeSpan.Zero
78+
}, out SecurityToken validatedToken);
79+
80+
var jwtSecurityToken = (JwtSecurityToken)validatedToken;
81+
var userId = int.Parse(jwtSecurityToken.Claims.First(claim => claim.Type == "nameid").Value);
82+
return userId;
83+
}
84+
catch
85+
{
86+
return null;
87+
}
5588
}
5689

5790
private async Task<IEnumerable<Claim>> GetClaimsAsync(User user)
@@ -69,5 +102,12 @@ private async Task<IEnumerable<Claim>> GetClaimsAsync(User user)
69102

70103
return claims;
71104
}
105+
private static string GenerateRefreshToken()
106+
{
107+
var randomNumber = new byte[64];
108+
using var rng = RandomNumberGenerator.Create();
109+
rng.GetBytes(randomNumber);
110+
return Convert.ToBase64String(randomNumber);
111+
}
72112
}
73113
}

0 commit comments

Comments
 (0)