7
7
using System . Data ;
8
8
using System . IdentityModel . Tokens . Jwt ;
9
9
using System . Security . Claims ;
10
+ using System . Security . Cryptography ;
10
11
using System . Text ;
11
12
12
13
namespace ReactwithDotnetCore . Controllers
@@ -17,14 +18,14 @@ public class LoginController(IConfiguration configuration) : Controller
17
18
18
19
[ AllowAnonymous ]
19
20
[ HttpPost ( "userlogin" ) ]
20
- public IActionResult UserLogin ( [ FromBody ] User login )
21
+ public async Task < IActionResult > UserLogin ( [ FromBody ] User login )
21
22
{
22
23
IActionResult response = Unauthorized ( ) ;
23
- var user = AuthenticateUser ( login ) ;
24
+ var user = await AuthenticateUser ( login ) ;
24
25
if ( user != null )
25
26
{
26
- var tokenString = GenerateJSONWebToken ( user ) ;
27
- response = Ok ( new { message = "success" , token = tokenString } ) ;
27
+ var ( tokenString , refreshToken ) = GenerateTokens ( user ) ;
28
+ response = Ok ( new { message = "success" , token = tokenString , refreshToken } ) ;
28
29
}
29
30
return response ;
30
31
}
@@ -56,10 +57,50 @@ public async Task<IActionResult> UserRegister([FromBody] User register)
56
57
}
57
58
}
58
59
59
- private string GenerateJSONWebToken ( User userInfo )
60
+ /// <summary>
61
+ /*
62
+ *
63
+ The purpose of a refresh token is to provide a way to obtain a new access token without requiring the
64
+ user to re-enter their credentials.Access tokens have a limited lifespan, and when they expire, the
65
+ user would typically need to log in again to get a new access token.
66
+
67
+ With a refresh token mechanism, when the access token expires, the client can use the refresh token
68
+ to obtain a new access token without requiring the user's credentials. This helps in maintaining a
69
+ balance between security and user convenience. The refresh token is a long-lived token that can
70
+ be securely stored by the client and used to request new access tokens as needed.
71
+ *
72
+ */
73
+ /// </summary>
74
+ /// <param name="refreshTokenRequest"></param>
75
+ /// <returns></returns>
76
+ [ AllowAnonymous ]
77
+ [ HttpPost ( "refreshtoken" ) ]
78
+ public IActionResult RefreshToken ( [ FromBody ] RefreshTokenRequest refreshTokenRequest )
79
+ {
80
+ IActionResult response = BadRequest ( "Invalid token" ) ;
81
+ var principal = GetPrincipalFromExpiredToken ( refreshTokenRequest . Token ) ;
82
+
83
+ if ( principal != null )
84
+ {
85
+ var username = principal ? . Claims ? . FirstOrDefault ( c => c . Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" ) ? . Value ;
86
+ if ( username != null )
87
+ {
88
+ var user = GetUserByUsername ( username ) ;
89
+
90
+ if ( user != null && refreshTokenRequest . RefreshToken == user . RefreshToken )
91
+ {
92
+ var ( tokenString , newRefreshToken ) = GenerateTokens ( user ) ;
93
+ response = Ok ( new { token = tokenString , refreshToken = newRefreshToken } ) ;
94
+ }
95
+ }
96
+ }
97
+
98
+ return response ;
99
+ }
100
+
101
+ private ( string tokenString , string refreshToken ) GenerateTokens ( User userInfo )
60
102
{
61
- // Ensure the key has at least 256 bits
62
- var securityKey = new SymmetricSecurityKey ( Encoding . UTF8 . GetBytes ( configuration ? [ "Jwt:Key" ] ? . PadRight ( 32 ) ) ) ;
103
+ var securityKey = new SymmetricSecurityKey ( Encoding . UTF8 . GetBytes ( configuration [ "Jwt:Key" ] ? . PadRight ( 32 ) ?? string . Empty ) ) ;
63
104
var credentials = new SigningCredentials ( securityKey , SecurityAlgorithms . HmacSha256 ) ;
64
105
65
106
var claims = new [ ] {
@@ -75,20 +116,74 @@ private string GenerateJSONWebToken(User userInfo)
75
116
expires : DateTime . Now . AddMinutes ( 120 ) ,
76
117
signingCredentials : credentials ) ;
77
118
78
- return new JwtSecurityTokenHandler ( ) . WriteToken ( token ) ;
119
+ var refreshToken = GenerateRefreshToken ( ) ;
120
+ userInfo . RefreshToken = refreshToken ; // Save refresh token to user in your data store
121
+
122
+ // Update the refresh token in the database
123
+ UpdateRefreshTokenInDatabase ( userInfo . Username , refreshToken ) ;
124
+
125
+ return ( new JwtSecurityTokenHandler ( ) . WriteToken ( token ) , refreshToken ) ;
79
126
}
80
127
81
- private User AuthenticateUser ( User login )
128
+ private static string GenerateRefreshToken ( )
129
+ {
130
+ // Generate a random refresh token (you may use a more sophisticated method)
131
+ var randomNumber = new byte [ 32 ] ;
132
+ using var rng = RandomNumberGenerator . Create ( ) ;
133
+ rng . GetBytes ( randomNumber ) ;
134
+ return Convert . ToBase64String ( randomNumber ) ;
135
+ }
136
+
137
+ private ClaimsPrincipal GetPrincipalFromExpiredToken ( string token )
138
+ {
139
+ var tokenValidationParameters = new TokenValidationParameters
140
+ {
141
+ ValidateIssuer = true ,
142
+ ValidateAudience = true ,
143
+ ValidateLifetime = false , // This will allow an expired token to be parsed
144
+ ValidateIssuerSigningKey = true ,
145
+ ValidIssuer = configuration [ "Jwt:Issuer" ] ,
146
+ ValidAudience = configuration [ "Jwt:Audience" ] ,
147
+ IssuerSigningKey = new SymmetricSecurityKey ( Encoding . UTF8 . GetBytes ( configuration [ "Jwt:Key" ] ?? string . Empty ) )
148
+ } ;
149
+
150
+ var tokenHandler = new JwtSecurityTokenHandler ( ) ;
151
+
152
+ // The following line will throw an exception if the token is expired
153
+ var principal = tokenHandler . ValidateToken ( token , tokenValidationParameters , out SecurityToken securityToken ) ;
154
+
155
+ return principal ;
156
+ }
157
+
158
+ private async Task < User > AuthenticateUser ( User login )
82
159
{
83
160
using IDbConnection dbConnection = new SqlConnection ( _connectionString ) ;
84
161
dbConnection . Open ( ) ;
85
162
86
- // Example: Authenticate user based on Username and Password
87
163
string query = "SELECT * FROM TBLB_User WITH(NOLOCK) WHERE Username = @Username AND Password = @Password" ;
88
- var users = dbConnection . Query < User > ( query , new { login . Username , login . Password } ) ;
164
+ var users = await dbConnection . QueryAsync < User > ( query , new { login . Username , login . Password } ) ;
165
+
166
+ return users . FirstOrDefault ( ) ?? new User ( ) ;
167
+ }
168
+
169
+ private User GetUserByUsername ( string username )
170
+ {
171
+ using IDbConnection dbConnection = new SqlConnection ( _connectionString ) ;
172
+ dbConnection . Open ( ) ;
173
+
174
+ string query = "SELECT * FROM TBLB_User WITH(NOLOCK) WHERE Username = @Username" ;
175
+ var user = dbConnection . Query < User > ( query , new { Username = username } ) . FirstOrDefault ( ) ;
176
+
177
+ return user ?? new User ( ) ;
178
+ }
179
+
180
+ private void UpdateRefreshTokenInDatabase ( string username , string newRefreshToken )
181
+ {
182
+ using IDbConnection dbConnection = new SqlConnection ( _connectionString ) ;
183
+ dbConnection . Open ( ) ;
89
184
90
- // Assuming there should be only one matching user
91
- return users . FirstOrDefault ( ) ;
185
+ string updateQuery = "UPDATE TBLB_User SET RefreshToken = @RefreshToken WHERE Username = @Username" ;
186
+ dbConnection . Execute ( updateQuery , new { RefreshToken = newRefreshToken , Username = username } ) ;
92
187
}
93
188
}
94
189
}
0 commit comments