Login And Role Based Custom Authentication In ASP.NET Core 3.1

Introduction

 
This article explains how to log in with JWT (json web token) and set custom authentication for every role logged in into the web application in ASP.NET Core 3.1.
 
So, for login, we will see how to get a JWT token with user claims and store it in the session storage key “JWToken”, then apply the authentication filter by Role, assign it to that user and restrict to another user unauthorized user and how to logout users.
 

Overview

 
JWT-(Json Web Token)
  • JWToken is the JSON Format string value.
  • JWToken is issued for each valid user and created once during user login.
  • JWToken is used for HTTP requests and is valid until the user logs out.
Authentication- Authentication is the process of determining who you are.
 
Claims-based authentication
 
Claims are a statement about or a property of a particular identity. That consists of name and value for ex. You could have UserName claim, EmailAddress claim, or Role claim.
  • This is a single identity that contains a no. of claims.
Let’s start with an example:
 
Step 1
 
Create an ASP.NET Core web application with MVC in .NET Core 3.1.
 
Step 2
 
Create Login Controller.cs and make a login view over the index action.
  1. public class LoginController : BaseController    
  2.     {    
  3.         ApplicationDbContext db;    
  4.         public LoginController(ApplicationDbContext db)    
  5.         {    
  6.             this.db = db;    
  7.         }     
  8.  [HttpGet, AllowAnonymous]    
  9.         public IActionResult Index()    
  10.         {    
  11.             return View();    
  12.         }    
  13. }    
Step 3
 
Create a BaseController.cs and create an asynced "CreateAuthenticationTicket " function.
  1.  public class BaseController : Controller  
  2.     {  
  3.  public async Task CreateAuthenticationTicket(AdminLogin user)  
  4.         {  
  5.             var key = Encoding.ASCII.GetBytes(SiteKeys.Token);  
  6.             var JWToken = new JwtSecurityToken(  
  7.             issuer: SiteKeys.WebSiteDomain,  
  8.             audience: SiteKeys.WebSiteDomain,  
  9.             claims: GetUserClaims(user),  
  10.             notBefore: new DateTimeOffset(DateTime.Now).DateTime,  
  11.             expires: new DateTimeOffset(DateTime.Now.AddDays(1)).DateTime,  
  12.             signingCredentials: new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)  
  13.         );  
  14.             
  15.             var token = new JwtSecurityTokenHandler().WriteToken(JWToken);  
  16.             HttpContext.Session.SetString("JWToken", token);  
  17.         }  
  18.   
  19.   
  20.   private IEnumerable<Claim> GetUserClaims(AdminLogin user)  
  21.         {  
  22.             List<Claim> claims = new List<Claim>();  
  23.             Claim _claim;  
  24.             _claim = new Claim(ClaimTypes.Name, user.UserName);  
  25.             claims.Add(_claim);  
  26.   
  27.             _claim = new Claim("Role", Role.Admin);  
  28.             claims.Add(_claim);  
  29.   
  30.             return claims.AsEnumerable<Claim>();  
  31.         }  
  32. }  
 
 
 
 
 Create a Role struct in BaseController.cs and set different roles under this.
  1. public struct Role  
  2.       {  
  3.           public const string Admin = "Admin";  
  4.           public const string Guest = "Guest";  
  5.       }  
Create Sitekeys.cs class for getting configurations from the appsettings.json file
 
SiteKeys.cs 
  1. using Microsoft.Extensions.Configuration;  
  2. using System;  
  3. using System.Collections.Generic;  
  4. using System.Linq;  
  5. using System.Threading.Tasks;  
  6.   
  7. namespace DemoIntro.Models  
  8. {  
  9.     public class SiteKeys  
  10.     {  
  11.         private static IConfigurationSection _configuration;  
  12.         public static void Configure(IConfigurationSection configuration)  
  13.         {  
  14.             _configuration = configuration;  
  15.         }  
  16.   
  17.         public static string WebSiteDomain => _configuration["WebSiteDomain"];  
  18.         public static string Token => _configuration["Secret"];  
  19.          
  20.     }  
  21. }  
Step 4
 
Now create the Login Post method  
  1. [HttpPost, AllowAnonymous]    
  2.        public IActionResult LoginNow(AdminLogin model)    
  3.        {    
  4.            var admin = db.AdminLogin.Where(x => x.UserName.Trim() == model.UserName.Trim() && x.Password ==model.Password).FirstOrDefault();    
  5.            try    
  6.            {    
  7.                if (admin != null)    
  8.                {    
  9.                    _ = CreateAuthenticationTicket(admin);    
  10.                   // Show Success Message -"Welcome!"    
  11.                    return RedirectToAction(nameof(ProfileController.CreateEditProfile), "Profile");    
  12.                }    
  13.                else    
  14.                {    
  15.                  //  Show Error Message- "Invalid Credentials."    
  16.                    return View("Index", model);    
  17.                }    
  18.            }    
  19.            catch (Exception ex)    
  20.            {    
  21.                //Show Error Message- ex.Message    
  22.                return View("Index", model);    
  23.            }    
  24.        }   
Step 5
 
Create index.cshtml 
  1. @model DemoIntro.Data.Tables.AdminLogin    
  2. @{    
  3.     Layout = null;    
  4. }    
  5.     
  6. <!DOCTYPE html>    
  7. <html>    
  8. <head>    
  9.        
  10. </head>    
  11. <body class="hold-transition login-page">    
  12.   <div class="login-box">    
  13.    <div class="card">    
  14.             <div class="card-body login-card-body">    
  15.                 <p class="login-box-msg">Sign in to start your session</p>    
  16.     
  17.                 <form id="frmLogin" asp-action="LoginNow" asp-controller="Login" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post">    
  18.     
  19.     
  20.                     <div class="form-group">    
  21.                         <div class="input-group mb-3">    
  22.                             <input asp-for="@Model.UserName" class="form-control" placeholder="Email">    
  23.                             <div class="input-group-append">    
  24.                                 <div class="input-group-text">    
  25.                                     <span class="fas fa-envelope"></span>    
  26.                                 </div>    
  27.                             </div>    
  28.                         </div>    
  29.                         <span asp-validation-for="@Model.UserName" class="text-danger"></span>    
  30.                     </div>    
  31.     
  32.     
  33.                     <div class="form-group">    
  34.                         <div class="input-group mb-3">    
  35.                             <input asp-for="@Model.Password" class="form-control" placeholder="Password">    
  36.                             <div class="input-group-append">    
  37.                                 <div class="input-group-text">    
  38.                                     <span class="fas fa-lock"></span>    
  39.                                 </div>    
  40.                             </div>    
  41.                         </div>    
  42.                         <span asp-validation-for="@Model.Password" class="text-danger"></span>    
  43.                     </div>    
  44.     
  45.     
  46.                     <div class="row">    
  47.                          
  48.                         <!-- /.col -->    
  49.                         <div class="col-4">    
  50.                             <button type="submit" class="btn btn-primary btn-block">Sign In</button>    
  51.                         </div>    
  52.                         <!-- /.col -->    
  53.                     </div>    
  54.                 </form>    
  55.             </div>    
  56.             <!-- /.login-card-body -->    
  57.         </div>    
  58.     </div>    
  59. </body>    
  60. </html>    
Step 6
 
Set a few JWT authentication tags in Startup.cs file. services.AddAuthentication ,services.AddSession
  1. public void ConfigureServices(IServiceCollection services)    
  2.      {    
  3.          services.AddDbContext<ApplicationDbContext>(options =>    
  4.              options.UseSqlServer(    
  5.                  Configuration.GetConnectionString("DefaultConnection")));    
  6.          services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)    
  7.              .AddEntityFrameworkStores<ApplicationDbContext>();    
  8.          services.AddControllersWithViews();    
  9.          services.AddRazorPages();    
  10.   
  11.   
  12.          #region "JWT Token For Authentication Login"    
  13.          SiteKeys.Configure(Configuration.GetSection("AppSettings"));    
  14.          var key = Encoding.ASCII.GetBytes(SiteKeys.Token);    
  15.     
  16.          services.AddSession(options =>    
  17.          {    
  18.              options.IdleTimeout = TimeSpan.FromMinutes(60);    
  19.          });    
  20.     
  21.     
  22.          services.AddAuthentication(auth =>    
  23.          {    
  24.              auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;    
  25.              auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;    
  26.          })    
  27.           .AddJwtBearer(token =>    
  28.           {    
  29.               token.RequireHttpsMetadata = false;    
  30.               token.SaveToken = true;    
  31.               token.TokenValidationParameters = new TokenValidationParameters    
  32.               {    
  33.                   ValidateIssuerSigningKey = true,    
  34.                   IssuerSigningKey = new SymmetricSecurityKey(key),    
  35.                   ValidateIssuer = true,    
  36.                   ValidIssuer = SiteKeys.WebSiteDomain,    
  37.                   ValidateAudience = true,    
  38.                   ValidAudience = SiteKeys.WebSiteDomain,    
  39.                   RequireExpirationTime = true,    
  40.                   ValidateLifetime = true,    
  41.                   ClockSkew = TimeSpan.Zero    
  42.               };    
  43.           });    
  44.   
  45.          #endregion    
  46.     
  47.      }     
Add JWToken in Authorization Bearer in the request header:
  1. public void Configure(IApplicationBuilder app, IWebHostEnvironment env)    
  2.       {    
  3.           if (env.IsDevelopment())    
  4.           {    
  5.               app.UseDeveloperExceptionPage();    
  6.               app.UseDatabaseErrorPage();    
  7.           }    
  8.           else    
  9.           {    
  10.               app.UseExceptionHandler("/Home/Error");    
  11.               // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.    
  12.               app.UseHsts();    
  13.           }    
  14.           app.UseHttpsRedirection();    
  15.           app.UseStaticFiles();    
  16.           app.UseRouting();    
  17.   
  18.           #region "JWT Token For Authentication Login"    
  19.     
  20.           app.UseCookiePolicy();    
  21.           app.UseSession();    
  22.           app.Use(async (context, next) =>    
  23.           {    
  24.               var JWToken = context.Session.GetString("JWToken");    
  25.               if (!string.IsNullOrEmpty(JWToken))    
  26.               {    
  27.                   context.Request.Headers.Add("Authorization""Bearer " + JWToken);    
  28.               }    
  29.               await next();    
  30.           });    
  31.           app.UseAuthentication();    
  32.           app.UseAuthorization();    
  33.            
  34.   
  35.           #endregion    
  36.     
  37.           app.UseEndpoints(endpoints =>    
  38.           {    
  39.               //Routing Area Admin    
  40.               endpoints.MapAreaControllerRoute(    
  41.                     name: "routeArea",    
  42.                     areaName: "Admin",    
  43.                     pattern: "Admin/{controller=Home}/{action=Index}/{id?}");    
  44.               endpoints.MapRazorPages();    
  45.     
  46.               //Routing Front     
  47.               endpoints.MapControllerRoute(    
  48.                   name: "default",    
  49.                   pattern: "{controller=Main}/{action=Index}/{id?}");    
  50.               endpoints.MapRazorPages();    
  51.           });    
  52.     
  53.       }   
 
Step 7
 
Add connection and configurations in the appsettings.json file.  
  1. {  
  2.   "ConnectionStrings": {  
  3.    
  4.     "DefaultConnection""Server=LOKESH007\\LOKESH007;Database=DemoIntro;user=sa;password=admin@1234;Trusted_Connection=False;"  
  5.   
  6.   },  
  7.   "Logging": {  
  8.     "LogLevel": {  
  9.       "Default""Information",  
  10.       "Microsoft""Warning",  
  11.       "Microsoft.Hosting.Lifetime""Information"  
  12.     }  
  13.   },  
  14.   "AllowedHosts""*",  
  15.   
  16.   "AppSettings": {  
  17.     "Secret""WriteMySecretKeyForAuthentication",  
  18.     "WebSiteDomain""http://localhost/Demo/",  
  19.     "WebSiteName""khushbu Saini"  
  20.   }  
  21.   
  22. }  
Step 8
 
Now create our custom Authorize filter validating each user role, restrict unauthorized users, auto redirect to login if session timeout or unauthorized request hit.
 
Authorize.cs  
  1. using Microsoft.AspNetCore.Http;  
  2. using Microsoft.AspNetCore.Mvc;  
  3. using Microsoft.AspNetCore.Mvc.Filters;  
  4. using System;  
  5. using System.Collections.Generic;  
  6. using System.Linq;  
  7. using System.Net;  
  8. using System.Security.Claims;  
  9. using System.Threading.Tasks;  
  10.   
  11. namespace DemoIntro.filters  
  12. {  
  13.     public class AuthorizeAttribute : TypeFilterAttribute  
  14.     {  
  15.         public AuthorizeAttribute(params string[] claim) : base(typeof(AuthorizeFilter))  
  16.         {  
  17.             Arguments = new object[] { claim };  
  18.         }  
  19.     }  
  20.   
  21.     public class AuthorizeFilter : IAuthorizationFilter  
  22.     {  
  23.         readonly string[] _claim;  
  24.   
  25.         public AuthorizeFilter(params string[] claim)  
  26.         {  
  27.             _claim = claim;  
  28.         }  
  29.   
  30.         public void OnAuthorization(AuthorizationFilterContext context)  
  31.         {  
  32.             var IsAuthenticated = context.HttpContext.User.Identity.IsAuthenticated;  
  33.             var claimsIndentity = context.HttpContext.User.Identity as ClaimsIdentity;  
  34.   
  35.             if (IsAuthenticated)  
  36.             {  
  37.                 bool flagClaim = false;  
  38.                 foreach (var item in _claim)  
  39.                 {  
  40.                     if (context.HttpContext.User.HasClaim("Role", item))  
  41.                         flagClaim = true;  
  42.                 }  
  43.                 if (!flagClaim)  
  44.                 {  
  45.                     if (context.HttpContext.Request.IsAjaxRequest())  
  46.                         context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; //Set HTTP 401   
  47.                     else  
  48.                         context.Result = new RedirectResult("~/Login/Index");  
  49.                 }  
  50.             }  
  51.             else  
  52.             {  
  53.                 if (context.HttpContext.Request.IsAjaxRequest())  
  54.                 {  
  55.                     context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; //Set HTTP 403 -   
  56.                 }  
  57.                 else  
  58.                 {  
  59.                     context.Result = new RedirectResult("~/Login/Index");  
  60.                 }  
  61.             }  
  62.             return;  
  63.         }  
  64.     }  
  65. }  
 
Step 9
 
Set the access role to each user controller
  1.  [Authorize(Role.Admin)]  
  2.     public class AchievementsController : BaseController  
  3.     {  
  4. ////  
  5. }  
  1. [Authorize(Role.Guest)]  
  2.    public class AccessoriesController : BaseController  
  3.    {/// 
Step 10
 
Finally, the logout function for each use logout is here:
  1. [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]  
  2.        public IActionResult Logout()  
  3.        {  
  4.            HttpContext.Session.Clear();  
  5.            return RedirectToAction("Index");  
  6.        }  
Output
 
Here is the login page, I have the first login with the admin. It shows me successful login and redirects to the admin dashboard.
 
 
Now, click on Accessories Controller. Because I have provided the access of this controller to Guest only, the admin can't unable to access it and auto-redirect to login view.
 
 

Summary

 
In this article, I discussed how we can get log in with multiple identity claim users. We also saw how we create our custom authentication filter so that we can custom authenticate each user while logging in and also validate that user role. Furthermore, we saw how to restrict other unauthorized users for login and also restrict them with URL rewriting. At last, we saw how to log out.