Implementing JWT Auth In Web API

Introduction

I was trying to implement JWT Auth in the Web API in my Angular 2 client-side application. But while searching on the internet, I could not find a simple solution. Finally, I learned and implemented the process successfully. Here, I am sharing the steps involved in solving this problem. This will help you and will definitely save you time too.

Source code.

Prerequisites 

  1. Web API in ASP.NET Core with JWT Authentication Project solution.
  2. Angular2/4 for a client-side application.

See the project structure below.

solution explorer

Step 1 - Create ASP.NET Core Web API Project

  1. Open Visual Studio 2017 and go to File >> New >> Project
  2. Select the project template
    .NET Framework
  3. Right-click the Solution Explorer and select Add -> New Project->Class Library.
    JSON Web Token

Fitness.JWT.API Project

I would like to explain the highlighted part of the project source code for enabling JWT Authentication.


JSON Web Token

Using the code

Blocks of code should look like this.

startup.cs

Configuring secret key, allowing cross-origin, and applying User policy authentication.

//  
 public IConfigurationRoot Configuration { get; }  
        //SecretKey  for Authentication  
        private const string SecretKey = "ABCneedtogetthisfromenvironmentXYZ";  
        private readonly SymmetricSecurityKey _signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey));  
        // This method gets called by the runtime. Use this method to add services to the container.  
        public void ConfigureServices(IServiceCollection services)  
        {  
            // Add framework services.  
            // services.AddMvc();  
            // Add framework services.  
            // Add framework services.  
            services.AddCors(options =>  
            {  
                options.AddPolicy("CorsPolicy",//Allow Cross origin  
                    builder => builder.AllowAnyOrigin()  
                    .AllowAnyMethod()  
                    .AllowAnyHeader()  
                    .AllowCredentials());  
            });  
  
            services.AddOptions();  
  
            // Make authentication compulsory across the board (i.e. shut  
            // down EVERYTHING unless explicitly opened up).  
            services.AddMvc(config =>  
            {  
                var policy = new AuthorizationPolicyBuilder()  
                                 .RequireAuthenticatedUser()  
                                 .Build();  
                config.Filters.Add(new AuthorizeFilter(policy));  
            });  
  
            // Use policy auth.  
            services.AddAuthorization(options =>  
            {  
                options.AddPolicy("FitnessJWT",  
                                  policy => policy.RequireClaim("FitnessJWT", "FitnessUser"));  
            });  
  
            // Get options from app settings  
            var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));  
  
            // Configure JwtIssuerOptions  
            services.Configure<JwtIssuerOptions>(options =>  
            {  
                options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];  
                options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)];  
                options.SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256);  
            });  
        }  
//  

JwtIssuerOptions.cs

This is the class file that is responsible for creating an Auth unique ticket on the server.
  1. //  
  2.   public class JwtIssuerOptions  
  3. {  
  4.     /// <summary>  
  5.     /// "iss" (Issuer) Claim  
  6.     /// </summary>  
  7.     /// <remarks>The "iss" (issuer) claim identifies the principal that issued the  
  8.     ///   JWT.  The processing of this claim is generally application specific.  
  9.     ///   The "iss" value is a case-sensitive string containing a StringOrURI  
  10.     ///   value.  Use of this claim is OPTIONAL.</remarks>  
  11.     public string Issuer { get; set; }  
  12.   
  13.     /// <summary>  
  14.     /// "sub" (Subject) Claim  
  15.     /// </summary>  
  16.     /// <remarks> The "sub" (subject) claim identifies the principal that is the  
  17.     ///   subject of the JWT.  The claims in a JWT are normally statements  
  18.     ///   about the subject.  The subject value MUST either be scoped to be  
  19.     ///   locally unique in the context of the issuer or be globally unique.  
  20.     ///   The processing of this claim is generally application specific.  The  
  21.     ///   "sub" value is a case-sensitive string containing a StringOrURI  
  22.     ///   value.  Use of this claim is OPTIONAL.</remarks>  
  23.     public string Subject { get; set; }  
  24.   
  25.     /// <summary>  
  26.     /// "aud" (Audience) Claim  
  27.     /// </summary>  
  28.     /// <remarks>The "aud" (audience) claim identifies the recipients that the JWT is  
  29.     ///   intended for.  Each principal intended to process the JWT MUST  
  30.     ///   identify itself with a value in the audience claim.  If the principal  
  31.     ///   processing the claim does not identify itself with a value in the  
  32.     ///   "aud" claim when this claim is present, then the JWT MUST be  
  33.     ///   rejected.  In the general case, the "aud" value is an array of case-  
  34.     ///   sensitive strings, each containing a StringOrURI value.  In the  
  35.     ///   special case when the JWT has one audience, the "aud" value MAY be a  
  36.     ///   single case-sensitive string containing a StringOrURI value.  The  
  37.     ///   interpretation of audience values is generally application specific.  
  38.     ///   Use of this claim is OPTIONAL.</remarks>  
  39.     public string Audience { get; set; }  
  40.   
  41.     /// <summary>  
  42.     /// "nbf" (Not Before) Claim (default is UTC NOW)  
  43.     /// </summary>  
  44.     /// <remarks>The "nbf" (not before) claim identifies the time before which the JWT  
  45.     ///   MUST NOT be accepted for processing.  The processing of the "nbf"  
  46.     ///   claim requires that the current date/time MUST be after or equal to  
  47.     ///   the not-before date/time listed in the "nbf" claim.  Implementers MAY  
  48.     ///   provide for some small leeway, usually no more than a few minutes, to  
  49.     ///   account for clock skew.  Its value MUST be a number containing a  
  50.     ///   NumericDate value.  Use of this claim is OPTIONAL.</remarks>  
  51.     public DateTime NotBefore => DateTime.UtcNow;  
  52.   
  53.     /// <summary>  
  54.     /// "iat" (Issued At) Claim (default is UTC NOW)  
  55.     /// </summary>  
  56.     /// <remarks>The "iat" (issued at) claim identifies the time at which the JWT was  
  57.     ///   issued.  This claim can be used to determine the age of the JWT.  Its  
  58.     ///   value MUST be a number containing a NumericDate value.  Use of this  
  59.     ///   claim is OPTIONAL.</remarks>  
  60.     public DateTime IssuedAt => DateTime.UtcNow;  
  61.   
  62.     /// <summary>  
  63.     /// Set the timespan the token will be valid for (default is 3 min/180 seconds)  
  64.     /// </summary>  
  65.     public TimeSpan ValidFor { get; set; } = TimeSpan.FromMinutes(1);  
  66.   
  67.     /// <summary>  
  68.     /// "exp" (Expiration Time) Claim (returns IssuedAt + ValidFor)  
  69.     /// </summary>  
  70.     /// <remarks>The "exp" (expiration time) claim identifies the expiration time on  
  71.     ///   or after which the JWT MUST NOT be accepted for processing.  The  
  72.     ///   processing of the "exp" claim requires that the current date/time  
  73.     ///   MUST be before the expiration date/time listed in the "exp" claim.  
  74.     ///   Implementers MAY provide for some small leeway, usually no more than  
  75.     ///   a few minutes, to account for clock skew.  Its value MUST be a number  
  76.     ///   containing a NumericDate value.  Use of this claim is OPTIONAL.</remarks>  
  77.     public DateTime Expiration => IssuedAt.Add(ValidFor);  
  78.   
  79.     /// <summary>  
  80.     /// "jti" (JWT ID) Claim (default ID is a GUID)  
  81.     /// </summary>  
  82.     /// <remarks>The "jti" (JWT ID) claim provides a unique identifier for the JWT.  
  83.     ///   The identifier value MUST be assigned in a manner that ensures that  
  84.     ///   there is a negligible probability that the same value will be  
  85.     ///   accidentally assigned to a different data object; if the application  
  86.     ///   uses multiple issuers, collisions MUST be prevented among values  
  87.     ///   produced by different issuers as well.  The "jti" claim can be used  
  88.     ///   to prevent the JWT from being replayed.  The "jti" value is a case-  
  89.     ///   sensitive string.  Use of this claim is OPTIONAL.</remarks>  
  90.     public Func<Task<string>> JtiGenerator =>  
  91.       () => Task.FromResult(Guid.NewGuid().ToString());  
  92.   
  93.     /// <summary>  
  94.     /// The signing key to use when generating tokens.  
  95.     /// </summary>  
  96.     public SigningCredentials SigningCredentials { get; set; }  
  97. }  
  98. //  

JwtController.cs

it is a controller where the anonymous users will log in and which creates the JWT security token, encodes it and sends it back to the client as a response with policy.

identity.FindFirst("FitnessJWT")

Look into the below code.
  1. [HttpPost]  
  2.        [AllowAnonymous]  
  3.        public async Task<IActionResult> Get([FromBody] ApplicationUser applicationUser)  
  4.        {  
  5.            var identity = await GetClaimsIdentity(applicationUser);  
  6.            if (identity == null)  
  7.            {  
  8.                _logger.LogInformation($"Invalid username ({applicationUser.UserName}) or password ({applicationUser.Password})");  
  9.                return BadRequest("Invalid credentials");  
  10.            }  
  11.   
  12.            var claims = new[]  
  13.            {  
  14.        new Claim(JwtRegisteredClaimNames.Sub, applicationUser.UserName),  
  15.        new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()),  
  16.        new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64),  
  17.        identity.FindFirst("FitnessJWT")  
  18.      };  
  19.   
  20.            // Create the JWT security token and encode it.  
  21.            var jwt = new JwtSecurityToken(  
  22.                issuer: _jwtOptions.Issuer,  
  23.                audience: _jwtOptions.Audience,  
  24.                claims: claims,  
  25.                notBefore: _jwtOptions.NotBefore,  
  26.                expires: _jwtOptions.Expiration,  
  27.                signingCredentials: _jwtOptions.SigningCredentials);  
  28.   
  29.            var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);  
  30.   
  31.            // Serialize and return the response  
  32.            var response = new  
  33.            {  
  34.                access_token = encodedJwt,  
  35.                expires_in = (int)_jwtOptions.ValidFor.TotalSeconds,  
  36.                State=1,  
  37.                expire_datetime= _jwtOptions.IssuedAt  
  38.            };  
  39.   
  40.            var json = JsonConvert.SerializeObject(response, _serializerSettings);  
  41.            return new OkObjectResult(json);  
  42.        }  

JwtAuthTestController.cs

This is the controller where I have defined the policy [Authorize(Policy = "FitnessJWT")]. So, when a user requests to the Controller, then it matches the policy and secret key. Then only the response is returned to the client.

  1. [HttpGet("[action]")]  
  2.       [Authorize(Policy = "FitnessJWT")]  
  3.       public IActionResult WeatherForecasts()  
  4.       {  
  5.           var rng = new Random();  
  6.   
  7.           List<WeatherForecast> lstWeatherForeCast = new List<WeatherForecast>();  
  8.           for (int i = 0; i < 5; i++)  
  9.           {  
  10.               WeatherForecast obj = new WeatherForecast();  
  11.               obj.DateFormatted = DateTime.Now.AddDays(i).ToString("d");  
  12.               obj.TemperatureC = rng.Next(-20, 55);  
  13.               obj.Summary = Summaries[rng.Next(Summaries.Length)];  
  14.               lstWeatherForeCast.Add(obj);  
  15.           }  
  16.   
  17.           var response = new  
  18.           {  
  19.               access_token = lstWeatherForeCast,  
  20.               State = 1  
  21.           };  
  22.   
  23.           var json = JsonConvert.SerializeObject(response, _serializerSettings);  
  24.           return new OkObjectResult(json);  
  25.   
  26.       }  

Step 2 - Angular2/4 for Client-side application.

I would like to add one note that I have not focused on the UI part too much but I have tried to implement JWT Auth from Angular 2/4 Application.

Fitness.App.UI Solution

login.component.ts

This is the login module with TypeScript where we authenticate a user by passing Username and password.

  1.     import { Component } from '@angular/core';  
  2. import { Router } from '@angular/router';  
  3. import { AuthService } from "../../../app/services/auth.service";  
  4. import { LoginModel } from "../../model/login.model";  
  5. @Component({  
  6.     selector: 'Fitness-Login',  
  7.     templateUrl: './login.component.html',  
  8.     styleUrls: ['./login.component.css'],  
  9.     providers: [AuthService]  
  10. })  
  11. export class LoginComponent {  
  12.     loginModel = new LoginModel();  
  13.     constructor(private router: Router, private authService: AuthService) {  
  14.     }  
  15.     login() {  
  16.         this.authService.login(this.loginModel.userName, this.loginModel.password)  
  17.             .then(result => {  
  18.                 if (result.State == 1) {  
  19.                     this.router.navigate(["/nav-menu"]);  
  20.                 }  
  21.                 else {  
  22.                     alert(result.access_token);  
  23.                 }  
  24.             });  
  25.     }  
  26. }  

auth.service.ts

The authentication service validates the credentials and redirects to the homepage.

  1. login(userName: string, password: string): Promise<ResponseResult> {  
  2.     let data = {  
  3.         "userName": userName,  
  4.         "password": password  
  5.     }  
  6.     let headers = new Headers({ 'Content-Type': 'application/json' });  
  7.     let applicationUser = JSON.stringify(data);  
  8.     let options = new RequestOptions({ headers: headers });  
  9.   
  10.     if (this.checkLogin()) {  
  11.         return this.authPost(this.localUrl + '/api/Jwt', applicationUser, options);  
  12.     }  
  13.     else {  
  14.         return this.http.post(this.localUrl + '/api/Jwt', applicationUser, options).toPromise()  
  15.             .then(  
  16.             response => {  
  17.                 let result = response.json() as ResponseResult;  
  18.                 if (result.State == 1) {  
  19.                     let json = result.access_token as any;  
  20.                     localStorage.setItem(this.tokeyKey, json);  
  21.                     localStorage.setItem(this.tokeyExpKey, result.expire_datetime);  
  22.                     this.sg['isUserExist'] = true;  
  23.                 }  
  24.                 return result;  
  25.             }  
  26.             )  
  27.             .catch(this.handleError);  
  28.     }  
  29. }  

app.module.client.ts

{ provide: 'ORIGIN_URL', useValue: 'http://localhost:57323' }, path on the JWT WEB API.

You need to change the localhost API based on your machine URL.

  1.   @NgModule({  
  2.     bootstrap: sharedConfig.bootstrap,  
  3.     declarations: sharedConfig.declarations,  
  4.     imports: [  
  5.         BrowserModule,  
  6.         FormsModule,  
  7.         HttpModule,  
  8.         ...sharedConfig.imports  
  9.     ],  
  10.     providers: [  
  11.         //{ provide: 'ORIGIN_URL', useValue: location.origin },  
  12.         { provide: 'ORIGIN_URL', useValue: 'http://localhost:57323' },  
  13.         AuthService, AuthGuard, SimpleGlobal  
  14.     ]  
  15. })  
  16. export class AppModule {  
  17. }  

To run the application, you need to set he project as below.

Run the solution with multiple startup projects. Then, in the browser,  both - the client app and the Web API Service will start in two tabs.

JSON Web Token

The output of the application is given below.

JSON Web Token
JSON Web Token
User Name: Test

Password - Test

Then, it will redirect you to the nav menu page as below.

JSON Web Token

Up Next
    Ebook Download
    View all
    Learn
    View all