Blog

You are here: Blog / How to Implement JWT Authentication using Asp.net Core and Angular 8 – Part 1

  • How to Implement JWT Authentication using Asp.net Core and Angular 8 – PART 1

    From the first part1 we are creating asp.net core application which contain all the setting of the JWT authentication at API level and in part 2 we are going to check how to use JWT authentication API in Angular project.

    Here we consider that you know the swagger and Asp.net Identity.

    Step 1: Create Asp.net Core application using visual studio


    Step 2: After Create new application install nuget package of following dlls if not exists.

    • MIcrosoft.AspNetCore.All
    • Microsoft.EntityFrameWorkCore
    • Microsoft.EntityFrameworkCore.SqlServer
    • Microsoft.EntityFrameworkCore.Tools


    Step 3: Flow chart explanation of how the JWT authentication work


    Step 4: Flow chart process

    Login:
    • Login with user name and password
    • Authenticate the user name and password.
    • If login successful return with authentication token if not throw error.


    Step 5: Create models for database as here we use code first approach

    Create two files as mentioned below.

    • Models/IdentityUserModel.cs
    • Models/DatabaseContext.cs


    Step 6: Configure IdentityUserModel as below

    using Microsoft.AspNetCore.Identity;
    using System.Collections.Generic;
    public class IdentityUserModel: IdentityUser
    {
    	public ICollection<ApplicationUserRole> UserRoles { get; set; }
    }
    
    public class ApplicationUserRole : IdentityUserRole<string>
    {
    	public virtual IdentityUserModel User { get; set; }
    	public virtual ApplicationRole Role { get; set; }
    }
    
    public class ApplicationRole : IdentityRole
    {
    	public ICollection<ApplicationUserRole> UserRoles { get; set; }
    }
    

    If you do not want to use role then remove role related code.


    Step 7: Configure DatabaseContext.cs class file.

    using Microsoft.AspNetCore.Identity;
    using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Configuration;
    using Microsoft.AspNetCore.Http;
    public class DatabaseContext: IdentityDbContext<IdentityUserModel, ApplicationRole, string, IdentityUserClaim<string>,
    ApplicationUserRole, IdentityUserLogin<string>,
    IdentityRoleClaim<string>, IdentityUserToken<string>>
    {
        private IHttpContextAccessor accessor;
        private IConfiguration configuration;
            
        public DatabaseContext(DbContextOptions options, IHttpContextAccessor _accessor, IConfiguration _configuration)
        : base(options)
        {
            accessor = _accessor;
            configuration = _configuration;
        }
         
        public DbSet<UserModel> UsersInfo { get; set; }
            
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            var host = accessor.HttpContext?.Request.Host;
            if (host!=null && host.ToString().IndexOf(configuration["APIUATUrl"]) >= 0)
            {
                optionsBuilder.UseSqlServer(configuration.GetConnectionString("UATConnectionString"));
            }
            else if (host != null && host.ToString().IndexOf(configuration["APIProductionUrl"]) >= 0)
            {
                optionsBuilder.UseSqlServer(configuration.GetConnectionString("ProductionConnectionString"));
            }
        }
     
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Entity<ApplicationUserRole>(userRole =>
            {
                userRole.HasKey(ur => new {ur.UserId, ur.RoleId});
     
                userRole.HasOne(ur => ur.Role)
                        .WithMany(r => r.UserRoles)
                        .HasForeignKey(ur => ur.RoleId)
                        .IsRequired();
     
                userRole.HasOne(ur => ur.User)
                        .WithMany(r => r.UserRoles)
                        .HasForeignKey(ur => ur.UserId)
                        .IsRequired();
            });
        }
    }


    Step 8: Configuring Asp.Net Identity in ConfigureServices method of Startup.cs

    using Microsoft.EntityFrameworkCore;
    using [projectName].Models;
    

    [projectName] - Replace with project namespace

    string connectionString = Configuration.GetConnectionString("DatabaseConnectionString ");
    services.AddDbContext<DatabaseContext>(opts => opts.UseSqlServer(connectionString));
    services.AddIdentity<IdentityUserModel, ApplicationRole>()
    .AddEntityFrameworkStores<DatabaseContext>()
    .AddDefaultTokenProviders();

    Configuring JWT in ConfigureServices method of Startup.cs

    using System;
    using System.Text;
    using Microsoft.AspNetCore.Authentication.JwtBearer;
    using Microsoft.IdentityModel.Tokens;
    using System.IdentityModel.Tokens.Jwt;
    JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); 
    services.AddAuthentication(options =>
    		{
    			options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    			options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
    			options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    			options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    		})
    		.AddJwtBearer(cfg =>
    		{
    			cfg.RequireHttpsMetadata = false;
    			cfg.SaveToken = true;
    			cfg.TokenValidationParameters = new TokenValidationParameters
    			{
    				ValidateIssuer = true,
    				ValidateAudience = true,
    				ValidateLifetime = true,
    				ValidateIssuerSigningKey = true, 
    				ValidIssuer = Configuration["JwtIssuer"],
    				ValidAudience = Configuration["JwtAudience"],
    				IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtKey"])),
    				ClockSkew = TimeSpan.Zero, 
    				SaveSigninToken = true
    			};
    		});
    

    Connectionstring, Configuration["JwtIssuer"], Configuration["JwtAudience"], Configuration["JwtKey"] are configured in appsettings.json as below

    "ConnectionStrings": {
        "DatabaseConnectionString": "Server=[Database_Server];Database=[Database_Name];User ID=[User_Name];Password=[Password]”
    }
    "APIUrl": "api-url",
    "JwtIssuer": "[Issuer_Url]",
    "JwtAudience": "[Audience_Url]",
    "JwtKey": "secreatekeyComehere",
    "JwtExpireHours": 12

    Set the values for these

    • Database_Server
    • Database_Name
    • User_Name
    • Password
    • Issuer_Url
    • Audience_Url
    • JWT key has to be changed to maintain uniqueness

    Add below line in configure method of Startup.cs

    app.UseAuthentication();

    Configure User Model as below in ConfigureService method of Startup.cs

    using Microsoft.AspNetCore.Identity;
    var builder = services.AddIdentityCore<IdentityUserModel>(o =>
    					{
    						o.Password.RequireDigit = true;
    						o.Password.RequireLowercase = false;
    						o.Password.RequireUppercase = true;
    						o.Password.RequireNonAlphanumeric = true;
    						o.Password.RequiredLength = 6;
    						o.User.RequireUniqueEmail = true;
    					});
    builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services);
    builder.AddEntityFrameworkStores<DatabaseContext>().AddDefaultTokenProviders();
    

    Note: Update the settings for password complexity in the above code based on the requirement.


    Step 9: Process of JWT Authentication and Token creation.

    To configure Login endpoints create below files

    • ViewModels/LoginViewModel.cs
    • Helper/Response.cs
    • Controllers/AuthController.cs

    Add LoginViewModel.cs class file

    public class LoginViewModel
    {
    	public string Email { get; set; }
    	public string Password { get; set; }
    } 
    

    Add Response.cs class file

    public class Response
    {
    	public bool success { get; set; }
    	public string message { get; set; }
    }
     
    

    Create controller as AuthController.cs with login method as below

    using Microsoft.AspNetCore.Identity;
    using System.IdentityModel.Tokens.Jwt;
    using System.Security.Claims;
    using Microsoft.IdentityModel.Tokens;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.Extensions.Configuration;
    using Microsoft.AspNetCore.Mvc;
    using System.Threading.Tasks;
    using System.Text;
    using [projectName].Models;
    using [projectName].ViewModels
     
    

    [projectName] - Replace with project namespace

    [Produces("application/json")]
    [Route("api/auth")]
    public class AuthController : Controller
    {
    	private readonly SignInManager<IdentityUserModel> signInManager;
    	private readonly UserManager<IdentityUserModel> userManager;
    	private readonly IConfiguration configuration;
    	public AuthController(UserManager<IdentityUserModel> _userManager, 
    	SignInManager<IdentityUserModel> _signInManager,
    	IConfiguration _configuration)
    	{
    		userManager = _userManager;
    		signInManager = _signInManager; 
    		configuration = _configuration; 
    	} 
    
    	[HttpPost]
    	[Route("login")]
    	public async Task<object> Login([FromBody] LoginViewModel model)
    	{
    		bool enableLockout = false;
    		var user = await userManager.FindByEmailAsync(model.Email);
    		if (user != null)
    		{
    			var result = await signInManager.PasswordSignInAsync(user, model.Password, false, enableLockout);
    			if (result.Succeeded)
    			{ 
    				return await generateJwtToken(model.Email, user);
    			}
    		} 
    		var response = new
    		{
    			success = false,
    			errorMessage = "Login failed"
    		};
    		return Ok(response);
    	} 
    
    	[NonAction]
    	private async Task<object> generateJwtToken(string email, IdentityUserModel user)
    	{
    		var roles = await userManager.GetRolesAsync(user);
    		var claims = new List<Claim>
    		{
    			new Claim(JwtRegisteredClaimNames.Sub, email),
    			new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
    			new Claim(ClaimTypes.NameIdentifier, user.Id),
    			new Claim(ClaimTypes.Role, roles[0])
    		}; 
    		var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JwtKey"]));
    		var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
    		var expires = DateTime.UtcNow.AddHours(Convert.ToDouble(configuration["JwtExpireHours"])); 
    		var token = new JwtSecurityToken(configuration["JwtIssuer"],
    										configuration["JwtAudience"],
    										claims,
    										expires: expires,
    										signingCredentials: creds
    										);
    		var response = new { auth_token = new JwtSecurityTokenHandler().WriteToken(token),
    							 success = true
    						   };
    		return Ok(response);
    	}
    }
    
    


    Step 10: Generate Asp.net Identity table using Migration Script

    Run migration commands in Package Manager Console — command to enable migration PM> enable-migrations — command to add migration PM> Add-Migration [Migration_name] — command to update database PM> update-database

    — If any changes or mistake in migration, then use the below command to remove migration before updating the database i.e before executing update-database command. PM> remove-migration

    Above commands results in creation of Identity tables in database

    Note:

    • No extra configurations or settings are required to create Asp.net Identity tables. Those are taken care by .Net Framework itself.
    • Add migration will create a file with the all the changes has to be done on the database. Below screen shot is one of the examples of that.
    • With the migration name a record is created in the __EFMigrationsHistory table

    Before testing the login api, User and roles has to be created. Create roles required in the database. Table name AspNetRoles.

    INSERT INTO AspNetRoles (Id,[Name],NormalizedName)
    Values('6B108034-FAE8-46BD-B1E3-F5A957EDE43E','User','User')
    


    Step 11: Testing login API

    Run the application, login with valid user name and password which returns JWT token if not throws error. Click on Try it out button

    Enter valid user name and password

    Response body is returned with authentication token

    Add authorize annotation above the controller method for which authorization requires.

    [HttpGet]
    [Route("testAuthorize")]
    [Authorize]
    public async Task<IActionResult> testAuthorize()
    { 
      return Ok(new { success = true, message = 'Success' });
    }
    
    From part 2 we can see how to use those api setting in Angular 8