Implement JWT Token Authentication with Validate and Refresh Token in asp.net mvc
Implement JWT Token Authentication with Validate and Refresh Token in asp.net mvc
JSON Web Tokens (JWT) have become an increasingly popular method for authentication and authorization in modern web applications. JWT provides a compact and self-contained way to securely transmit information between parties as a JSON object. In this article, we will explore how to implement JWT token-based authentication in an ASP.NET MVC application, including validating incoming tokens and automatically refreshing expired tokens.
Token-based authentication offers several advantages over traditional cookie-based or session-based authentication mechanisms. Tokens are stateless, meaning they contain all the necessary information for authentication within the token itself, reducing the need for server-side storage of session data. This makes token-based authentication more scalable and suitable for distributed systems.
The JWT token structure consists of three parts: a header, a payload, and a signature. The header contains metadata about the token, such as the algorithm used for signing. The payload contains claims or pieces of information about the user, such as their name, email, or roles. The signature is used to verify the integrity of the token and ensure it hasn't been tampered with.
When implementing JWT token authentication in ASP.NET MVC, we will start by configuring the application to generate JWT tokens upon successful user authentication. We'll then cover how to validate incoming tokens on subsequent requests to ensure they are valid and have not been tampered with. This involves verifying the token's signature and checking its expiration time.
Additionally, we'll explore how to implement token refreshing, a mechanism that allows clients to obtain a new token before the current one expires. This ensures a seamless user experience without requiring frequent re-authentication. We'll discuss strategies for securely refreshing tokens and handling expired tokens gracefully.
By the end of this article, you'll have a solid understanding of how to implement JWT token-based authentication in your ASP.NET MVC applications, including validation, expiration handling, and token refreshing. This will provide a secure and scalable authentication solution for your web applications.
Installing JWT
JSON Web Token (JWT) is an open standard that defines a compact way for securely transmitting information between parties as a JSON object. It is often used in web applications to securely keep user-related data or claims which can be verified easily,
To get started, we need to install the necessary NuGet package for JWT authentication in our ASP.NET MVC project, as shown in the image below:-
After installing the required package, you need to configure the JWT settings for your application. These settings can be added either directly in your code or more commonly, in the Web.config file. For this article, we will add the JWT configurations to the Web.config file. The most crucial setting is the JWT secret key, which should be a long, random string used for encrypting and decrypting the tokens. You can generate a secure key using online tools or create a sufficiently long string manually.
<appSettings> <add key="config:JwtKey" value="C1CF4B7DC4C4175B6618DE4F55CA4"/> <add key="config:JwtExpireDays" value="30"/> <add key="config:JwtIssuer" value="https://localhost:44318"/> <add key="config:JwtAudience" value="SecureApiUser"/> </appSettings>
Use GenerateJwtToken method from Authenticate class to generate token
Next, we'll implement the logic to generate JWT tokens upon successful user authentication. In your login controller or the controller responsible for handling user logins, you can include the following code snippet. Notice that we're calling a method named Authentication.GenerateJwtToken, which we'll define in the subsequent step. This method will be responsible for creating and returning a new JWT token based on the authenticated user's claims and credentials.
using JWTAuth.Models; using System; using System.Configuration; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Threading.Tasks; using System.Web.Mvc; namespace JWTAuth.Controllers { public class AccountController : Controller { public ActionResult Index() { return View(); } // POST: /account/login [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Login(AccountLoginModel viewModel) { try { if (!ModelState.IsValid) return View("index", viewModel); string encryptedPwd = viewModel.Password; var userPassword = Convert.ToString(ConfigurationManager.AppSettings["config:Password"]); var userName = Convert.ToString(ConfigurationManager.AppSettings["config:Username"]); if (encryptedPwd.Equals(userPassword) && viewModel.Email.Equals(userName)) { var roles = new string[] { "SuperAdmin", "Admin" }; var jwtSecurityToken = Authentication.GenerateJwtToken(userName, roles.ToList()); Request.Headers["Authorization"] = jwtSecurityToken; Session["LoginedIn"] = userName; Session["Token"] = jwtSecurityToken; return RedirectToAction("index", "Home"); } ModelState.AddModelError("", "Invalid username or password."); } catch (Exception e) { ModelState.AddModelError("", "Invalid username or password."); } return View("Index", viewModel); } public ActionResult Logout() { Session.Abandon(); return RedirectToAction("Index", "Account"); } } }
Implement Authentication class for creating methods to Generate Toke, Validate token and Refresh Token
Next, create an Authentication class within your application's Models folder. This class will encapsulate the logic for generating, validating, and refreshing JWT tokens. Add the following code to this class. The GenerateJwtToken method is responsible for creating a new JWT token based on the provided user claims. The ValidateToken method verifies the validity of an incoming JWT token, ensuring it hasn't been tampered with and hasn't expired. Lastly, the ReGenerateJwtToken method allows for seamless token renewal by generating a new token with updated expiration times when the current token is about to expire.
using Microsoft.IdentityModel.Tokens; using System; using System.Collections.Generic; using System.Configuration; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using System.Text; namespace JWTAuth.Models { public class Authentication { // Generate token public static string GenerateJwtToken(string username, List<string> roles) { var claims = new List<Claim> { new Claim(JwtRegisteredClaimNames.Sub, username), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(ClaimTypes.NameIdentifier, username) }; roles.ForEach(role => { claims.Add(new Claim(ClaimTypes.Role, role)); }); var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Convert.ToString(ConfigurationManager.AppSettings["config:JwtKey"]))); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var expires = DateTime.Now.AddMinutes(Convert.ToDouble(Convert.ToString(ConfigurationManager.AppSettings["config:JwtExpireDays"]))); var token = new JwtSecurityToken( Convert.ToString(ConfigurationManager.AppSettings["config:JwtIssuer"]), Convert.ToString(ConfigurationManager.AppSettings["config:JwtAudience"]), claims, expires: expires, signingCredentials: creds ); return new JwtSecurityTokenHandler().WriteToken(token); } public static string ReGenerateJwtToken(List<Claim> claims) { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Convert.ToString(ConfigurationManager.AppSettings["config:JwtKey"]))); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var expires = DateTime.Now.AddMinutes(Convert.ToDouble(Convert.ToString(ConfigurationManager.AppSettings["config:JwtExpireDays"]))); var token = new JwtSecurityToken( Convert.ToString(ConfigurationManager.AppSettings["config:JwtIssuer"]), Convert.ToString(ConfigurationManager.AppSettings["config:JwtAudience"]), claims, expires: expires, signingCredentials: creds ); return new JwtSecurityTokenHandler().WriteToken(token); } // Validate the token public static string ValidateToken(string token) { if (token == null) return null; var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes(Convert.ToString(ConfigurationManager.AppSettings["config:JwtKey"])); try { tokenHandler.ValidateToken(token, new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(key), ValidateIssuer = false, ValidateAudience = false, // set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later) ClockSkew = TimeSpan.Zero }, out SecurityToken validatedToken); var jwtToken = (JwtSecurityToken)validatedToken; var jti = jwtToken.Claims.First(claim => claim.Type == "jti").Value; var userName = jwtToken.Claims.First(sub => sub.Type == "sub" || sub.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier").Value; // return user id from JWT token if validation successful return userName; } catch { // return null if validation fails return null; } } } }
Implement JwtAuthorizationFilter for automatically validating and refreshing the token
To ensure proper token validation and automatic token refreshing, we will create a custom action filter attribute. This attribute will be responsible for validating the JWT token on each incoming request to the application. Additionally, it will automatically refresh the token if it's about to expire, ensuring a seamless user experience without frequent re-authentication prompts. You can copy the following code
using System; using System.Configuration; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using System.Text; using System.Web; using System.Web.Mvc; using System.Web.Routing; using Microsoft.IdentityModel.Tokens; namespace JWTAuth.Models { public class JwtAuthorizationFilter : AuthorizeAttribute, IAuthorizationFilter { public override void OnAuthorization(AuthorizationContext filterContext) { var token = HttpContext.Current.Request.Headers["Authorization"] ?? Convert.ToString(HttpContext.Current.Session["Token"]); if (string.IsNullOrEmpty(token)) { RedirectToLoginPage(filterContext); return; } var isTokenValid = Authentication.ValidateToken(token); if (string.IsNullOrWhiteSpace(isTokenValid)) { var refreshedToken = RefreshToken(token); if (string.IsNullOrEmpty(refreshedToken)) { filterContext.Result = new HttpUnauthorizedResult(); return; } HttpContext.Current.Session["Token"] = refreshedToken; HttpContext.Current.Request.Headers["Authorization"] = refreshedToken; } } private void RedirectToLoginPage(AuthorizationContext filterContext) { filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Account", action = "Index" })); } private string RefreshToken(string expiredToken) { try { var principal = GetPrincipalFromExpiredToken(expiredToken); var claims = principal.Claims; return Authentication.ReGenerateJwtToken(claims.ToList()); } catch (SecurityTokenValidationException ex) { // Handle token validation errors // Log the error or return null if refreshing failed return null; } catch (Exception ex) { // Handle other exceptions // Log the error or return null if refreshing failed return null; } } private ClaimsPrincipal GetPrincipalFromExpiredToken(string token) { var tokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = false, ValidateIssuerSigningKey = true, ValidIssuer = ConfigurationManager.AppSettings["config:JwtIssuer"], ValidAudience = ConfigurationManager.AppSettings["config:JwtAudience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(ConfigurationManager.AppSettings["config:JwtKey"])) }; var tokenHandler = new JwtSecurityTokenHandler(); SecurityToken securityToken; return tokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken); } } }
Add Attribute JwtAuthorizationFilter on actions where you want to validate token
Now we just have to add the attribute JwtAuthorizationFilter on all actions wherever we want to validate the token
using JWTAuth.Models; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace JWTAuth.Controllers { public class HomeController : Controller { [JwtAuthorizationFilter] //Add when you want to validate token for this action public ActionResult Index(string token) { return View(); } [JwtAuthorizationFilter] //Add when you want to validate token for this action public ActionResult About() { ViewBag.Message = "Your application description page."; return View(); } public ActionResult Contact() { ViewBag.Message = "Your contact page."; return View(); } } }
Now run the application and log in with the correct credentials
You can see a unique token will be generated and you can also verify the information stored in the token in the next step
Paste the token generated by our application and here it will deserialize the encrypted information in a format that is readable. You can see here all the claims that were added by you while creating the JWT token.
You can wait till the token gets expired and after that when you will call the action it will again refresh the token and the application will keep working.So, this is how to Implement JWT Token Authentication with Validate and Refresh Token in asp.net mvc Or this is how to refresh jwt token when expired in asp.net mvc.