Introduction
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.
In this post, I specifically talk about.
- Authentication for an ASP.NET Core Web API
- Using JWT Tokens
- Using Role-Based Authorization
- Using only ASP.NET's low-level Auth features - not using ASP.NET Core Identity.
In this post, I follow the Repository Pattern.
Prerequisites
- .NET Core 6 SDK
- Visual Studio 2022
- Postman
Step-by-step Implementation
Step 1. Create a new .Net Core 6.0 Web API project.



Step 2. Project Structure.

Step 3. Install-Package
System.IdentityModel.Token.Jwt

Step 4. Authenticating users using a Web API endpoint.
- Next, we need to authenticate a user within the application by asking for Credentials, generating a Token, and returning it to the API client
- This likely happens in a controller Action Method or Middleware Endpoint Handler. Here's what this looks like using the controller Action Method
using Custom_Jwt_Token_Example.Models; using Custom_Jwt_Token_Example.Services; using Microsoft.AspNetCore.Mvc; namespace Custom_Jwt_Token_Example.Controllers { [Route("api/[controller]")] public class AuthenticationController : Controller { private readonly IAuthenticationService authenticationService; public AuthenticationController(IAuthenticationService authenticationService) { this.authenticationService = authenticationService; } [HttpPost] [Route("Login")] public AuthenticateResponse Login(AuthenticateRequest model) { return this.authenticationService.Authenticate(model); } } }
Implementing the Repository interface
- Create the IAuthentication interface under the Services Folder.
- This interface is planned to be an abstraction Layer over the Authentication Functionality.
using Custom_Jwt_Token_Example.Models; namespace Custom_Jwt_Token_Example.Services { public interface IAuthenticationService { AuthenticateResponse Authenticate(AuthenticateRequest model); } } - Create the IUserService interface under the Services Folder
- This interface is planned to be an abstraction Layer over the User Functionality.
using Custom_Jwt_Token_Example.Models; namespace Custom_Jwt_Token_Example.Services { public interface IUserService { User GetById(int id); IEnumerable<User> GetAll(); } } - I'm using the UserService class of the GetById method to actually Get user details by id.
using Custom_Jwt_Token_Example.Models; namespace Custom_Jwt_Token_Example.Services { public class UserService : IUserService { private List<User> _users = new List<User> { new User { Id = 1, FirstName = "mytest",Role= new List<Role>{Role.Customer}, LastName = "User", Username = "mytestuser", Password = "test123" }, new User { Id = 2, FirstName = "mytest2", LastName = "User2", Username = "test", Password = "test" } }; public IEnumerable<User> GetAll() { return _users; } public User GetById(int id) { return _users.FirstOrDefault(x => x.Id == id); } } } - Create a class called AuthenticationService in the Service Folder with the following code.
using Custom_Jwt_Token_Example.Helper; using Custom_Jwt_Token_Example.Models; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; namespace Custom_Jwt_Token_Example.Services { public class AuthenticationService : IAuthenticationService { private List<User> _users = new List<User> { new User { Id = 1, FirstName = "mytest", LastName = "User", Username = "mytestuser",Role= new List<Role>{Role.Customer} , Password = "test123" } }; private readonly AppSettings _appSettings; public AuthenticationService(IOptions<AppSettings> appSettings) { _appSettings = appSettings.Value; } public AuthenticateResponse Authenticate(AuthenticateRequest model) { var user = _users.SingleOrDefault(x => x.Username == model.UserName && x.Password == model.Password); if (user == null) return null; var token = generateToken(user); return new AuthenticateResponse() {Token= token}; } private string generateToken(User user) { var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_appSettings.Key)); var credetial = new SigningCredentials(securityKey,SecurityAlgorithms.HmacSha256); List<Claim> claims = new List<Claim>(){ new Claim("Id",Convert.ToString(user.Id)), new Claim(JwtRegisteredClaimNames.Sub, "Test"), new Claim(JwtRegisteredClaimNames.Email, "[email protected]"), //new Claim("Role", Convert.ToString(user.Role)), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) }; foreach (var role in user.Role) { claims.Add(new Claim("Role", Convert.ToString(role))); } var token = new JwtSecurityToken(_appSettings.Issuer, _appSettings.Issuer, claims, expires: DateTime.UtcNow.AddHours(1), signingCredentials: credetial); return new JwtSecurityTokenHandler().WriteToken(token); } } } - First, let's create a new class for our middleware component. In this example, let's assume we want to get a token from the header, validate the token, and attach it to the context of the user variable stored user information.
using Custom_Jwt_Token_Example.Services; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Text; namespace Custom_Jwt_Token_Example.Helper { public class JwtMiddleware { private readonly RequestDelegate _next; private readonly AppSettings _appSettings; public JwtMiddleware(RequestDelegate _next, IOptions<AppSettings> _appSettings) { this._next = _next; this._appSettings = _appSettings.Value; } public async Task Invoke(HttpContext context, IUserService userService) { var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last(); if (token != null) //Validate Token attachUserToContext(context, userService, token); _next(context); } private void attachUserToContext(HttpContext context, IUserService userService, string token) { try { var tokenHandler = new JwtSecurityTokenHandler(); var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_appSettings.Key)); tokenHandler.ValidateToken(token, new TokenValidationParameters { ValidateAudience = true, ValidateIssuer = true, ValidateIssuerSigningKey = true, IssuerSigningKey = key, ClockSkew = TimeSpan.Zero, ValidIssuer = _appSettings.Issuer, ValidAudience = _appSettings.Issuer }, out SecurityToken validateToken); var jwtToken = (JwtSecurityToken)validateToken; var userId = int.Parse(jwtToken.Claims.FirstOrDefault(_=>_.Type=="Id").Value); context.Items["User"] = userService.GetById(userId); } catch (Exception ex) { } } } } - When decorating an API endpoint with the [Authorization] attribute, the OnAuthorization method will be called each time before the API Endpoint is called.
- In this scenario, check user credentials and Role Validation. It returns Unathorization with a 401 status code. If the user does not exist and the role matches with the Authorize attribute,
using Custom_Jwt_Token_Example.Models; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; namespace Custom_Jwt_Token_Example.Helper { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class Authorization : Attribute, IAuthorizationFilter { private readonly IList<Role> _roles; public Authorization(params Role[] _roles) { this._roles = _roles?? new Role[]{ }; } public void OnAuthorization(AuthorizationFilterContext context) { var isRolePermission = false; User user = (User)context.HttpContext.Items["User"]; if (user == null) { context.Result = new JsonResult( new { Message = "Unauthorization" } ) { StatusCode = StatusCodes.Status401Unauthorized }; } if(user != null && this._roles.Any()) foreach (var userRole in user.Role) { foreach (var AuthRole in this._roles) { if (userRole == AuthRole) { isRolePermission = true; } } } if(!isRolePermission) context.Result = new JsonResult( new { Message = "Unauthorization" } ) { StatusCode = StatusCodes.Status401Unauthorized }; } } } - Add Decorator an API endpoint with [Authorization(Role. Customer)] attribute.
using Custom_Jwt_Token_Example.Helper; using Custom_Jwt_Token_Example.Models; using Microsoft.AspNetCore.Mvc; namespace Custom_Jwt_Token_Example.Controllers { [Authorization(Role.Customer)] [ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase { private static readonly string[] Summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; private readonly ILogger<WeatherForecastController> _logger; public WeatherForecastController(ILogger<WeatherForecastController> logger) { _logger = logger; } [HttpGet(Name = "GetWeatherForecast")] public IEnumerable<WeatherForecast> Get() { return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }) .ToArray(); } } } - In the Appsetting.Json File, add Appsetting Section JWT Token Key and Issuer.
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "AppSettings": { "Key": "986ghgrgtru989ASdsaerew13434545435", "Issuer": "TestIssuer" } }
Add Middleware
- Next, we need to register custom middleware using the Use<> extension method.
using Custom_Jwt_Token_Example.Helper; using Custom_Jwt_Token_Example.Services; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.Configure<AppSettings>(builder.Configuration.GetSection("AppSettings")); builder.Services.AddScoped<IAuthenticationService, AuthenticationService>(); builder.Services.AddScoped<IUserService,UserService>(); var app = builder.Build(); app.UseMiddleware<JwtMiddleware>(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run(); - I'm using the AuthenticationService class of the Authenticate method to actually generate a token.
- The code creates a token and extracts a string from it that's ready to be returned as bearer token value.
- The Token includes a username and role, which is what's required for ASP.Net Core's Authentication to work. I can then add some additional application-specific claims, if necessary, like the Display Name and User State object in the example below.
- Next, we need to create the AuthenticationResponse class used for the Token Value set and get.
namespace Custom_Jwt_Token_Example.Models { public class AuthenticateResponse { public string Token { get; set; } } } - Next, we need to create an AuthenticationRequest class used for the username and password sent to an authentication request.
using System.ComponentModel.DataAnnotations; namespace Custom_Jwt_Token_Example.Models { public class AuthenticateRequest { [Required] public string UserName { get; set; } [Required] public string Password { get; set; } } } - Next, we need to create a role enum used to store Admin User and Customer User.
namespace Custom_Jwt_Token_Example.Models { public enum Role { Admin, Customer } } - Next, we need to create a user class used for user information.
using System.Text.Json.Serialization; namespace Custom_Jwt_Token_Example.Models { public class User { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Username { get; set; } public List<Role> Role { get; set; } [JsonIgnore] public string Password { get; set; } } } - Next, we need to create an Appsetting class used for storing Appsetting Section Key And Issuer Values.
namespace Custom_Jwt_Token_Example.Helper { public class AppSettings { public string Key { get; set; } public string Issuer { get; set; } } }
Valid Token With Role Is Customer
So, at this point, I have authenticated with the Customer Role. I have restricted only Admin User Accessible to get an unauthorized error.

To Change Admin Role Of testUser Username


Amit KumarPosted Sep 20, 2024, 11:58 AM
Awesome article !!
Kishan ZalariyaPosted Oct 7, 2023, 8:27 AM
Awesome article!!!