JWT Authentication In Asp.Net Core Web Api



Published On Wednesday June 24, 2020 Reading Time: 5 minutes

JWT (JSON Web Token) is one of the popular and most used authentication and authorization mechanism in web development. The information exchanged via JWT can be verified and trusted as it is digitally signed using a secret (HMAC Algorithm) or public/private key pair using RSA or ECDSA. In the blog post, we will be discussing on how can JWT be incorporated in our .Net Core Application.

For the project, I am currently using Asp.Net Core 3.1 Api Project.

NuGetPackage associated with the tutorial:

  1. Microsoft.AspNetCore.Authentication.JwtBearer 3.1.5
  2. Microsoft.IdentityModel.Tokens 6.6.0
  3. Newtonsoft.Json 12.0.3
  4. System.IdentityModel.Tokens.Jwt 6.6.0

The folder structure for the entire application:

Jwt Authentication In Asp.Net Core Folder Structure

Appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "Jwt": {
    "Key": "veryVerySecretKey",  //Any String Can Be Used
    "Issuer": "http://localhost:63939/",
    "ExpiryMinutes": "1"
  }
}

The JWT node is the JWT setting.

Key: Signing key to generate the JWT token

Issuer: The valid issuer issuing the JWT token

ExpirtyMinutes: The token validity

Models

Model > TokenDetails.cs
public class TokenDetails
    {
        public string Name { get; set; }
        public string Email { get; set; }
        public string Roles { get; set; }
    }
Model > TokenRequest.cs
public class TokenRequest
    {
        public string Username { get; set; }
        public string Password { get; set; }
    }
Controller > AuthenticationController
[Route("api/auth")]
    [ApiController]
    public class AuthenticationController : ControllerBase
    {
        private readonly IConfiguration _configuration;
        public AuthenticationController(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        /// <summary>
        /// Get token, authenticating user credentials
        /// </summary>
        /// <param name="login"></param>
        /// <returns></returns>
        [Route("token")]
        [AllowAnonymous]
        [HttpPost]
        public IActionResult CreateToken([FromBody] TokenRequest login)
        {
            IActionResult response = Unauthorized();
            var user = AuthenticateUser(login);
            if (user.Name != null)
            {
                var claims = new Claim[]
                {
                    new Claim(ClaimTypes.Name, user.Name),
                    new Claim("Roles",user.Roles)
                };
                var tokenString = BuildToken(claims);
                response = Ok(tokenString);
            }
            return response;
        }
        /// <summary>
        /// Build token after successful credentials validation
        /// </summary>
        /// <param name="claims"></param>
        /// <returns></returns>
        private object BuildToken(Claim[] claims)
        {
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            var expiryTime = DateTime.Now.AddMinutes(Convert.ToDouble(_configuration["Jwt:ExpiryMinutes"]));
            var token = new JwtSecurityToken(_configuration["Jwt:Issuer"],
                _configuration["Jwt:Issuer"],
                expires: expiryTime,
                claims: claims,
                signingCredentials: creds);
            var details = new
            {
                tokenString = new JwtSecurityTokenHandler().WriteToken(token),
                expiryDate = expiryTime.ToString("MM/dd/yyyy HH:mm:ss")
            };
            return details;
        }

        public TokenDetails AuthenticateUser(TokenRequest tokenRequest)
        {
            var tokenDetails = new TokenDetails();
            if (tokenRequest.Username == "test" && tokenRequest.Password == "user")
            {
                tokenDetails.Email = "hello@rashik.com.np";
                tokenDetails.Name = "Rashik Tuladhar";
                tokenDetails.Roles =
                    "auth.weather"; //roles are separated by comma(,) which is also mentioned in constant class named AuthorizationConstants

            }
            return tokenDetails;
        }
    }

The authentication controller validates user credentials. Then generate the token for authentication. The AuthenticationUser is the method that validates the user and password for the authenticity of the credentials. We can use the database here for querying the username and password validity. And return the authenticity and role details as a comma-separated string. For the application example, I have simply hardcoded the username and password together with the roles string.

After validation of the user, claim values holds the full name as the Name and Roles as the role claims.

var claims = new Claim[] { new Claim(ClaimTypes.Name, user.Name), new Claim("Roles",user.Roles) };

Then the claims are used to generate the token and the token becomes the response for the token details request.

Startup.cs

Add the following code on the ConfigureService Method which is the JWT configuration

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
                {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuer = true, //validate the server that created that token
                        ValidateAudience = true, //ensure that the recipient of the token is authorized to receive it
                        ValidateLifetime = true, //check that the token is not expired and that the signing key of the issuer is valid
                        ValidateIssuerSigningKey = true, //verify that the key used to sign the incoming token is part of a list of trusted keys
                        ValidIssuer = Configuration["Jwt:Issuer"],
                        ValidAudience = Configuration["Jwt:Issuer"],
                        IssuerSigningKey =
                            new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"])),
                        RequireExpirationTime = true
                    };
                });

Add the following code on the Configure method so as to enable authentication on the Web API.

app.UseAuthentication();

The startup.cs class now looks like

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
                {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuer = true, //validate the server that created that token
                        ValidateAudience = true, //ensure that the recipient of the token is authorized to receive it
                        ValidateLifetime = true, //check that the token is not expired and that the signing key of the issuer is valid
                        ValidateIssuerSigningKey = true, //verify that the key used to sign the incoming token is part of a list of trusted keys
                        ValidIssuer = Configuration["Jwt:Issuer"],
                        ValidAudience = Configuration["Jwt:Issuer"],
                        IssuerSigningKey =
                            new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"])),
                        RequireExpirationTime = true
                    };
                });
            services.AddControllers();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseAuthentication();
            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }

Above all the configuration now enables the generation of the token for the validation. We can simply check the token generation via PostMan Application.

Token Generation Url:/api/auth/token

Postman Get Token:Jwt Token Check Via Postman

For instance, you can check the token details by pasting the token string on Jwt.io

Jwt Token Check With Details In Jwt.IO

Since we are using the default template of Asp.Net Core Web API we will have a WeatherForecastController by default, if we request the weatherforecast API, for now, we will simply get a response as

What if we want people to go through the authorization procedure so that only valid users get to see the response?. Let’s make a slight change on the WeatherForecastController.cs, add an attribute [Authorize]  on top of [ApiController] so the codes look like

[Authorize]
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };
        ......
     }

Now simply try the same postman call as above for weatherforecast  method

Unauthorized Access Without Jwt Token

We simply get the Unauthorized 401 status code, so the API method gets protected now for getting the details we need to add the token as the authorization parameter.

  1. First, simply get the token by calling the /api/auth/token by valid credentials.
  2. Go to the authorization tab right to Params menu and select the type as Bearer Token then add the token string to the Token text area on the right panel.
  3. Do take care of the expiryDate response on the token response, if it’s expired then you will be again returned with 401 status code.

Add Bearer Token On Postman For Authorization

Simple hit the send button then you will have a proper response and 200 status code.

Here ends how we can protect the API using JWT Token. We are still left with how to implement roles and authorize users with particular roles to get access to a specific method and yield the response.

Since the blog post has become quite long I am breaking the blog post into two parts which will be the continuation of this blog post.

If you want to try out the codes on your own I have added a GitHub Repo with the complete code which you can find in the link https://github.com/rashik-tuladhar/JWT-Authentication-In-Asp.Net-Core

Please find the link to the tutorial for the Authorization of Web Api With Roles Role Based Authorization Using Jwt Token In Asp.Net Core Web Api.

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x