JWT Authentication & Authorization in .NET Core 3.1

In this article, we will see how we can implement JWT authentication and authorization in .NET core.

What is JWT?

JWT is an acronym for JSON Web Token. JWT is a way for securely transmitting information between parties as a JSON object. This information is verified and trusted because it’s digitally signed. JWT’s can be using a secret (with the HMAC algorithm) or public/private key pair using RSA/ECDSA.

JWT structure contains three parts,

Header

Payload

Signature

It looks something like this,

{“token: “eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxOYW1lIjoiVmFpYmhhdiBCaGFwa2FyIiwicm9sZSI6IkFkbWluIiwianRpIjoiMTJjY2JmMWQtZDRhOS00ODUyLWE5YTgtMTRiY2Y3NzA0MmQ1IiwiZXhwIjoxNTg2NTAxNzkxLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo0NDMzNi8iLCJhdWQiOiJodHRwczovL2xvY2FsaG9zdDo0NDMzNi8ifQ.XS3LBDSRMhcJmUi2itgBbPPhrdbX2cFgC7tZ7X_einM”,“userDetails”:{“userName: “admin”,“fullName: “Vaibhav Bhapkar”,“password: “1234”,“userRole: “Admin”}

Let’s break these parts and dive deep into each of it,

Header:

The header consists of two-part one is signing algorithm (HMAC, SHA256, etc.) and the other one is the type of the token which is JWT.

Example:{“alg”: “HMAC”,“typ”: “JWT”}

This JSON form is Base64Url encoded to form the first part.

Payload:

The payload contains the claims. Claims are statements about an entity (typically, the user) and additional data. Again the payload formed JSON is Base64Url encoded to form second part.

Signature:

For the signature created you need to use encoded header, encoded payload, and secret with the algorithm specified at the header.

Example:HMAC(base64UrlEncode(header) + “.” +base64UrlEncode(payload),secret)

Putting all of these three parts together form Json Web Token.

JWT Authentication & Authorization in .NET Core 3.1

Requirements:

Visual studio or visual studio code installed with .Net Core 3.1

Steps to follow:

  1. Create a new project:

Visual Studio -> Create New Project -> Asp.Net Core Web application -> Select Web Api Template -> Create.

2. Install required a package of JWT from NuGet Package:

Right-click on project -> Manage NuGet package -> On browse section search for (Microsoft.AspNetCore.Authentication.JwtBearer) -> install.

3. Update Startup.cs ConfigureService() to register JWT authentication scheme,

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>{options.RequireHttpsMetadata = false;options.SaveToken = true;options.TokenValidationParameters = new TokenValidationParameters{ValidateIssuer = true,ValidateAudience = true,ValidateLifetime = true,ValidateIssuerSigningKey = true,ValidIssuer = Configuration[“Jwt:Issuer”],ValidAudience = Configuration[“Jwt:Audience”],IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration[“Jwt:SecretKey”])),ClockSkew = TimeSpan.Zero};});

Token validation parameters are described below,

4. A further step is to change in the appsettings.json with required details for JWT authentication scheme,

“Jwt”: {“SecretKey”: “VaibhavBhapkar”,“Issuer”: “https://localhost:44336/",“Audience”: “https://localhost:44336/"},

5. The next step is to make an authentication service available to the application to do this we need a call app.UseAuthentication in Configure() method,

public void Configure(IApplicationBuilder app, IWebHostEnvironment env){if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseHttpsRedirection();app.UseRouting();app.UseAuthentication();app.UseAuthorization();app.UseEndpoints(endpoints =>{endpoints.MapControllers();});}

6. Update Startup.cs file ConfigureService() method to add authorization code,

services.AddAuthorization(config =>{config.AddPolicy(Policies.Admin, Policies.AdminPolicy());config.AddPolicy(Policies.User, Policies.UserPolicy());});

For authorization we define policies class there we defined the policy defined according to roles as follows,

using Microsoft.AspNetCore.Authorization;using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;namespace JWTAuthenticationExample.Models{public class Policies{public const string Admin = “Admin”;public const string User = “User”;public static AuthorizationPolicy AdminPolicy(){return new AuthorizationPolicyBuilder().RequireAuthenticatedUser().RequireRole(Admin).Build();}public static AuthorizationPolicy UserPolicy(){return new AuthorizationPolicyBuilder().RequireAuthenticatedUser().RequireRole(User).Build();}}}

7. Generate JSON Web Token:

We have to create a new API controller called LoginController and defined Login() method inside it, this method is responsible for the generation of JWT. we need to mark this method as AllowAnonymous attribute to bypass the authentication this method expects the User model object with a username and password parameter.

We have to define one more method called AuthenticateUser() which will check for user exists or not and return User object with other details if exists. This AuthenticateUser() method called inside Login() method once the user is authenticated we have to define one more method called GenerateJWTToken() where we have created a JWT using the JwtSecurityToken class. we need to create an object of this class bypassing some of the parameters to constructor includes issuer, audience, claims, expires, and signing credentials.

Claims here are the data contained by token they are informed about the user which is used to authorize the access the resource. Claims are formed using key-value pairs here in our case we are storing full name and role using claims.

Finally, JwtSecurityTokenHandler.WriteToken method is used to generate the JWT. This method expects an object of the JwtSecurityToken class.

using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;namespace JWTAuthenticationExample.Models{public class User{public string UserName { get; set; }public string FullName { get; set; }public string Password { get; set; }public string UserRole { get; set; }}}
using System;using System.Collections.Generic;using System.IdentityModel.Tokens.Jwt;using System.Linq;using System.Security.Claims;using System.Text;using System.Threading.Tasks;using JWTAuthenticationExample.Models;using Microsoft.AspNetCore.Authorization;using Microsoft.AspNetCore.Mvc;using Microsoft.Extensions.Configuration;using Microsoft.Extensions.Logging;using Microsoft.IdentityModel.Tokens;namespace JWTAuthenticationExample.Controllers{[ApiController][Route(“[controller]”)]public class LoginController : ControllerBase{private readonly IConfiguration _config;private List<User> appUsers = new List<User>{new User { FullName = “Vaibhav Bhapkar”, UserName = “admin”, Password = “1234”, UserRole = “Admin” },new User { FullName = “Test User”, UserName = “user”, Password = “1234”, UserRole = “User” }};public LoginController(IConfiguration config){_config = config;}[HttpPost][AllowAnonymous]public IActionResult Login([FromBody]User login){IActionResult response = Unauthorized();User user = AuthenticateUser(login);if (user != null){var tokenString = GenerateJWTToken(user);response = Ok(new{token = tokenString,userDetails = user,});}return response;}User AuthenticateUser(User loginCredentials){User user = appUsers.SingleOrDefault(x => x.UserName == loginCredentials.UserName && x.Password == loginCredentials.Password);return user;}string GenerateJWTToken(User userInfo){var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config[“Jwt:SecretKey”]));var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);var claims = new[]{new Claim(JwtRegisteredClaimNames.Sub, userInfo.UserName),new Claim(“fullName”, userInfo.FullName.ToString()),new Claim(“role”,userInfo.UserRole),new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),};var token = new JwtSecurityToken(issuer: _config[“Jwt:Issuer”],audience: _config[“Jwt:Audience”],claims: claims,expires: DateTime.Now.AddMinutes(30),signingCredentials: credentials);return new JwtSecurityTokenHandler().WriteToken(token);}}}

8. Defining the methods according to the role to check authorization:

We have to create UserController which contains the method according to Roles i.e User and Admin. If you want to restrict the method access we can do this with Authorize attribute.

In our case, we are using policies to restrict access to methods.

using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using JWTAuthenticationExample.Models;using Microsoft.AspNetCore.Authorization;using Microsoft.AspNetCore.Mvc;using Microsoft.Extensions.Logging;namespace JWTAuthenticationExample.Controllers{[ApiController][Route(“[controller]”)]public class UserController : ControllerBase{[HttpGet][Route(“GetUserData”)][Authorize(Policy = Policies.User)]public IActionResult GetUserData(){return Ok(“This is a response from user method”);}[HttpGet][Route(“GetAdminData”)][Authorize(Policy = Policies.Admin)]public IActionResult GetAdminData(){return Ok(“This is a response from Admin method”);}}}

Result:

After posting requests with wrong data you will end up by getting 403 unauthorized errors.

After posting requests with the correct credential you will get a response with a generated token and a user object.

With the generated token of admin, you are allowed to access the only method which has access for admin.

With admin generated token if you accessing the user-accessible method you will end up with 403 access forbidden response.

Please click here to check out the code base for this.

Reference: https://www.c-sharpcorner.com/article/policy-based-authorization-with-angular-and-asp-net-core-using-jwt/

Thank You, See you in the next article !!

Technical Speaker | Computer Engineer | Full Stack Web Developer | ML Enthusiast | * Knowledge Shared = Knowledge² *