Web API Authentication Using JWT Token

JWT - (JSON Web Token)

JWT basically consists of three parts.

  • HEADER : ALGORITHM & TOKEN TYPE (JSON format which is encoded as a base64)
  • PAYLOAD : DATA (JSON format which is encoded as a base64.)
  • VERIFY SIGNATURE (Created and signed based on Header and Claims which is encoded as a base64.)

We use JWT just to verify that the request coming to our API is from a valid source. The above mentioned three parts are separated using dot (.) respectively in the encoded form.

It is basically used to encode our data using the algorithms we apply. JWT is very lightweight and can encode large amount of sensitive data which can even be passed as a query string.

Creation of JWT

  • Use the NuGet package called System.IdentityModel.Tokens.Jwt from MS to generate the token.
  • Validate for correct user from database and if validated, then generate the token by putting its information in payload and return that token to that user.
  • Add .cs file and write the below method to generate token.
    1. private  
    2. const string Secret = "db3OIsj+BXE9NZDy0t8W3TcNekrF+2d/1sFnWG4HnV8TZY30iTOdtVWJG8abWvB1GlOgJuQZdcF2Luqm/hccMw==";  
    3. public static string GenerateToken(string username, int expireMinutes = 20) {  
    4.     var symmetricKey = Convert.FromBase64String(Secret);  
    5.     var tokenHandler = new JwtSecurityTokenHandler();  
    6.     var now = DateTime.UtcNow;  
    7.     var tokenDescriptor = new SecurityTokenDescriptor {  
    8.         Subject = new ClaimsIdentity(new [] {  
    9.                 new Claim(ClaimTypes.Name, username)  
    10.             }),  
    11.             Expires = now.AddMinutes(Convert.ToInt32(expireMinutes)),  
    12.             SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(symmetricKey), SecurityAlgorithms.HmacSha256Signature)  
    13.     };  
    14.     var stoken = tokenHandler.CreateToken(tokenDescriptor);  
    15.     var token = tokenHandler.WriteToken(stoken);  
    16.     return token;  
    17. }  
  • The user will save the returned token either in session or local storage or in cookies, because for every new request, it will have to attach that token with request in header so that it can be validated with every new request. Thus, in this way, the API ensures that the incoming request is from a valid user.

Consumption of JWT

Add AuthorizationAttribute in WebApiConfigFile.

Eg

config.Filters.Add(new AuthorizeAttribute());

built JwtAuthenticationAttribute which inherits from IauthenticationFilter. With this attribute, you can authenticate any action. Just put this attribute on that action.

  1. public class ValueController: ApiController {  
  2.     [JwtAuthentication]  
  3.     public string Get() {  
  4.         return "value";  
  5.     }  
  6. }  

Below is the core method from authentication filter.

  1. private static bool ValidateToken(string token, out string username) {  
  2.     username = null;  
  3.     var simplePrinciple = JwtManager.GetPrincipal(token);  
  4.     var identity = simplePrinciple.Identity as ClaimsIdentity;  
  5.     if (identity == nullreturn false;  
  6.     if (!identity.IsAuthenticated) return false;  
  7.     var usernameClaim = identity.FindFirst(ClaimTypes.Name);  
  8.     username = usernameClaim ? .Value;  
  9.     if (string.IsNullOrEmpty(username)) return false;  
  10.     // More validate to check whether username exists in system  
  11.     return true;  
  12. }  
  13. protected Task < IPrincipal > AuthenticateJwtToken(string token) {  
  14.     string username;  
  15.     if (ValidateToken(token, out username)) {  
  16.         // based on username to get more information from database in order to build local identity  
  17.         var claims = new List < Claim > {  
  18.             new Claim(ClaimTypes.Name, username)  
  19.             // Add more claims if needed: Roles, ...  
  20.         };  
  21.         var identity = new ClaimsIdentity(claims, "Jwt");  
  22.         IPrincipal user = new ClaimsPrincipal(identity);  
  23.         return Task.FromResult(user);  
  24.     }  
  25.     return Task.FromResult < IPrincipal > (null);  
  26. }  

The workflow is using JWT library (NuGet package above) to validate the JWT token and then return back ClaimsPrincipal. We can perform more validation like checking whether user exists on our system and adding other custom validations if we want. The code is to validate JWT token and get principal back.

  1. public static ClaimsPrincipal GetPrincipal(string token) {  
  2.     try {  
  3.         var tokenHandler = new JwtSecurityTokenHandler();  
  4.         var jwtToken = tokenHandler.ReadToken(token) as JwtSecurityToken;  
  5.         if (jwtToken == nullreturn null;  
  6.         var symmetricKey = Convert.FromBase64String(Secret);  
  7.         var validationParameters = new TokenValidationParameters() {  
  8.             RequireExpirationTime = true,  
  9.                 ValidateIssuer = false,  
  10.                 ValidateAudience = false,  
  11.                 IssuerSigningKey = new SymmetricSecurityKey(symmetricKey)  
  12.         };  
  13.         SecurityToken securityToken;  
  14.         var principal = tokenHandler.ValidateToken(token, validationParameters, out securityToken);  
  15.         return principal;  
  16.     } catch (Exception) {  
  17.         //should write log  
  18.         return null;  
  19.     }  
  20. }  

If the JWT token is validated and the principal is returned, you should build a new local identity and put more information into it to check the role authorization.

 
 ---------------------Follow These Steps-------------------------
 
Step 1

Add package System.IdentityModel.Tokens.Jwt using nuget package manager
 
Step 2

Generate token while calling login API, 
  1. public class LoginModel {  
  2.     public string UserName {  
  3.         get;  
  4.         set;  
  5.     }  
  6.     public string AccessToken {  
  7.         get;  
  8.         set;  
  9.     }  
  10. }  
  11. [HttpPost, Route("api/account/login")]  
  12. public HttpResponseMessage login(string UserName, string Password) {  
  13.     var result = new DataResult < LoginModel > ();  
  14.     try {  
  15.         //Step 1: check from database using user id and password, is user is valid or not  
  16.         result = AccountService.login(UserName.ToLower().Trim(), Password.ToLower().Trim());  
  17.         //Step 2: if user is valid then generate token for him and return that token  
  18.         if (result.Success) result.Data.AccessToken = new JwtManager().GenerateToken(result.Data.UserName);  
  19.         return Request.CreateCustomResponse(result, result.StatusCode);  
  20.     } catch {  
  21.         result.Errors.Add("Invalid username or password");  
  22.         result.StatusCode = APIStatusCode.Unauthorized;  
  23.     }  
  24.     return Request.CreateCustomResponse(result, result.StatusCode);  
  25. }   
Step 3

Add a class JwtManager and a method to generate token, 
  1. public class JwtManager {  
  2.     //private const string Secret = "db3OIsj+BXE9NZDy0t8W3TcNekrF+2d/1sFnWG4HnV8TZY30iTOdtVWJG8abWvB1GlOgJuQZdcF2Luqm/hccMw==";  
  3.     private  
  4.     const string Secret = "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429090fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1";  
  5.     public string GenerateToken(string username, int expireMinutes = 20) {  
  6.         //http://stackoverflow.com/questions/18223868/how-to-encrypt-jwt-security-token  
  7.         var tokenHandler = new JwtSecurityTokenHandler();  
  8.         //create a identity and add claims to the user which we want to log in  
  9.         ClaimsIdentity claimsIdentity = new ClaimsIdentity(new [] {  
  10.             new Claim(ClaimTypes.Name, username)  
  11.         });  
  12.         //var securityKey = new SymmetricSecurityKey(System.Text.Encoding.Default.GetBytes(Secret));  
  13.         var securityKey = new SymmetricSecurityKey(Convert.FromBase64String(Secret));  
  14.         //create the jwt  
  15.         var token = (JwtSecurityToken)  
  16.         tokenHandler.CreateJwtSecurityToken(issuer: "http://abc.com", audience: "http://abc.com", subject: claimsIdentity, notBefore: DateTime.UtcNow, expires: DateTime.UtcNow.AddMinutes(expireMinutes), signingCredentials: new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature));  
  17.         var tokenString = tokenHandler.WriteToken(token);  
  18.         return tokenString;  
  19.     } 
Step 4

When token is generated, assign that token in Login Model and return that login model.
 
Step 5

Next step is to validate token: so add a new class in the root project named as TokenValidationHandler 
  1. internal class TokenValidationHandler: DelegatingHandler {  
  2.     private  
  3.     const string Secret = "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429090fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1";  
  4.     protected override Task < HttpResponseMessage > SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {  
  5.         HttpStatusCode statusCode;  
  6.         string token;  
  7.         //determine whether a jwt exists or not  
  8.         if (!TryRetrieveToken(request, out token)) {  
  9.             statusCode = HttpStatusCode.Unauthorized;  
  10.             //allow requests with no token - whether a action method needs an authentication can be set with the claimsauthorization attribute  
  11.             return base.SendAsync(request, cancellationToken);  
  12.         }  
  13.         try {  
  14.             SecurityToken securityToken;  
  15.             JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();  
  16.             TokenValidationParameters validationParameters = new TokenValidationParameters() {  
  17.                 // Validate the JWT Audience (aud) claim  
  18.                 ValidateAudience = true,  
  19.                     ValidAudience = "http://abc.com", //audienceConfig["Aud"],  
  20.                     // Validate the JWT Issuer (iss) claim  
  21.                     ValidateIssuer = true,  
  22.                     ValidIssuer = "http://abc.com", //audienceConfig["Iss"],  
  23.                     // Validate the token expiry  
  24.                     ValidateLifetime = true,  
  25.                     ClockSkew = TimeSpan.Zero,  
  26.                     LifetimeValidator = this.LifetimeValidator,  
  27.                     // The signing key must match!  
  28.                     ValidateIssuerSigningKey = true,  
  29.                     // IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.Default.GetBytes(Secret)),  
  30.                     IssuerSigningKey = new SymmetricSecurityKey(Convert.FromBase64String(Secret)),  
  31.             };  
  32.             //extract and assign the user of the jwt  
  33.             Thread.CurrentPrincipal = handler.ValidateToken(token, validationParameters, out securityToken);  
  34.             HttpContext.Current.User = Thread.CurrentPrincipal;  
  35.             return base.SendAsync(request, cancellationToken);  
  36.         } catch (SecurityTokenValidationException) {  
  37.             statusCode = HttpStatusCode.Unauthorized;  
  38.         } catch (Exception) {  
  39.             statusCode = HttpStatusCode.InternalServerError;  
  40.         }  
  41.         return Task < HttpResponseMessage > .Factory.StartNew(() => new HttpResponseMessage(statusCode) {});  
  42.     }  
  43.     private static bool TryRetrieveToken(HttpRequestMessage request, out string token) {  
  44.         token = null;  
  45.         IEnumerable < string > authzHeaders;  
  46.         if (!request.Headers.TryGetValues("Authorization", out authzHeaders) || authzHeaders.Count() > 1) {  
  47.             return false;  
  48.         }  
  49.         var bearerToken = authzHeaders.ElementAt(0);  
  50.         token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;  
  51.         return true;  
  52.     }  
  53.     public bool LifetimeValidator(DateTime ? notBefore, DateTime ? expires, SecurityToken securityToken, TokenValidationParameters validationParameters) {  
  54.         if (expires != null) {  
  55.             if (DateTime.UtcNow < expires) return true;  
  56.         }  
  57.         return false;  
  58.     }  
  59. }  
Step 6

Most Imp :  call it in web api config, 
  1. public static class WebApiConfig {  
  2.     public static void Register(HttpConfiguration config) {  
  3.         config.Filters.Add(new AuthorizeAttribute());  
  4.         config.MessageHandlers.Add(new TokenValidationHandler());  
  5.         // Web API routes  
  6.         config.MapHttpAttributeRoutes();  
  7.         config.Routes.MapHttpRoute(name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new {  
  8.             id = RouteParameter.Optional  
  9.         });  
  10.     }  
  11. }  
Step 7

Make sure WebApiConfig is called in global .asax,
  1. protected void Application_Start() {  
  2.     AreaRegistration.RegisterAllAreas();  
  3.     GlobalConfiguration.Configure(WebApiConfig.Register);  
  4.     FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);  
  5.     RouteConfig.RegisterRoutes(RouteTable.Routes);  
  6.     BundleConfig.RegisterBundles(BundleTable.Bundles);  
  7. }