77using System . Data ;
88using System . IdentityModel . Tokens . Jwt ;
99using System . Security . Claims ;
10+ using System . Security . Cryptography ;
1011using System . Text ;
1112
1213namespace ReactwithDotnetCore . Controllers
@@ -17,14 +18,14 @@ public class LoginController(IConfiguration configuration) : Controller
1718
1819 [ AllowAnonymous ]
1920 [ HttpPost ( "userlogin" ) ]
20- public IActionResult UserLogin ( [ FromBody ] User login )
21+ public async Task < IActionResult > UserLogin ( [ FromBody ] User login )
2122 {
2223 IActionResult response = Unauthorized ( ) ;
23- var user = AuthenticateUser ( login ) ;
24+ var user = await AuthenticateUser ( login ) ;
2425 if ( user != null )
2526 {
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 } ) ;
2829 }
2930 return response ;
3031 }
@@ -56,10 +57,50 @@ public async Task<IActionResult> UserRegister([FromBody] User register)
5657 }
5758 }
5859
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 )
60102 {
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 ) ) ;
63104 var credentials = new SigningCredentials ( securityKey , SecurityAlgorithms . HmacSha256 ) ;
64105
65106 var claims = new [ ] {
@@ -75,20 +116,74 @@ private string GenerateJSONWebToken(User userInfo)
75116 expires : DateTime . Now . AddMinutes ( 120 ) ,
76117 signingCredentials : credentials ) ;
77118
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 ) ;
79126 }
80127
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 )
82159 {
83160 using IDbConnection dbConnection = new SqlConnection ( _connectionString ) ;
84161 dbConnection . Open ( ) ;
85162
86- // Example: Authenticate user based on Username and Password
87163 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 ( ) ;
89184
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 } ) ;
92187 }
93188 }
94189}
0 commit comments