Thursday, April 9, 2020

Asp .Net Core WebApi using ADO .NET and Jwt Token Based Authentication

In this tutorial we are going to develop webapi using Asp .NetCore and protect the webmethods using JWT Token based Authentication. For this project i am using Visual Studio 2019 and Dotnet core version 3 and Sql server 2017.


Output:




Project Structure:


AuthenticateController:

This webapi controller class contain method authenticate, register and get all the user details. The [Authorize] attribute on the top of controller will prevent unauthorized api request. In order generated token we need to validate the user name and password for that we allow the Authenticate Method to accept the unauthorized with the help of [AllowAnonymous] request.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using JWTTokenAuthentication.Entities;
using JWTTokenAuthentication.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace JWTTokenAuthentication.Controllers
{
    [Authorize]
    [ApiController]
    [Route("[controller]")]
    public class AuthenticateController : ControllerBase 
    {
        private IUserService _userService;

        public AuthenticateController(IUserService userService)
        {
            _userService = userService;
        }

        [AllowAnonymous]
        [HttpPost("{menuid}/Auth")]
        public IActionResult Authenticate([FromBody]User userParam)
        {
            var user = _userService.Authenticate(userParam.Username, userParam.Password);

            if (user == null)
                return BadRequest(new { message = "username or password is incorrect" });

            return Ok(user);
        }

        [HttpPost("{menuid}/Registeruser")]
        public IActionResult RegisterUser([FromBody]User userParam)
        {
            string message = _userService.Register(userParam);
            return Ok("message="+message);
        }
     
        [HttpGet("{menuid}/GetAll")]     
        public IActionResult GetAll()
        {
            var users = _userService.GetAll();
            return Ok(users);
        }     
    }
}


User.Cs

The user class have properties to hold the values.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace JWTTokenAuthentication.Entities
{
    public class User
    {
        public int id { get; set; }
        [Required]
        public string Firstname { get; set; }
        [Required]
        public string Lastname { get; set; }
        [Required]
        public string Username { get; set; }
        [Required]
        public string Password { get; set; }
        public string Token { get; set; }

    }
}


Helpers.cs:

The AppSettings have property to store the Secret Key value for the Token Generation Algo and Dbstring to hold the Connection string.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace JWTTokenAuthentication.Services
{
    public class AppSettings
    {
        public string Secret { get; set; }

        public string DBString { get; set; }
    }
}


DBConnectService.cs

This class have methods to Register user and get user details using ADO.Net.

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using JWTTokenAuthentication.Entities;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;

namespace JWTTokenAuthentication.Services
{
    public class DBConnectService
    {

        public string sConnectionString { get; set; }

        public DBConnectService(string connectionstr)
        {
            sConnectionString = connectionstr;
        }

        public List<User> GetUsers()
        {
            List<User> _userDetails = new List<User>();            
            string connectionString = sConnectionString;
            DataTable dataTable = new DataTable();
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                connection.Open();
                SqlCommand command = new SqlCommand();
                command.CommandType = System.Data.CommandType.StoredProcedure;
                command.Connection = connection;
                command.CommandText = "USP_USERLIST";

                SqlDataAdapter sda = new SqlDataAdapter(command);
                sda.Fill(dataTable);
                _userDetails = ConvertDataTable<User>(dataTable);
            }
            return _userDetails;
        }//EOM GetUsers

        public string RegisterUser(SqlParameter[] sqlParameters, string sCommandType)
        {
            string sMessage = string.Empty;
            using (SqlConnection conn = new SqlConnection(sConnectionString))
            {
                using (SqlCommand command = new SqlCommand())
                {
                    command.CommandType = CommandType.StoredProcedure;
                    command.Connection = conn;
                    command.CommandText = sCommandType;
                    command.Parameters.Clear();
                    for (Int32 j = 0; j < sqlParameters.Length; j++)
                    {
                        command.Parameters.Add(sqlParameters[j]);
                    }                    

                    conn.Open();
                    DataTable dt = new DataTable();
                    SqlDataAdapter sda = new SqlDataAdapter(command);
                    sda.Fill(dt);

                    sMessage = dt.Rows[0][0].ToString();                    
                }
            }
            return sMessage;
        }//End RegisterUser

        private static List<T> ConvertDataTable<T>(DataTable dt)
        {
            List<T> data = new List<T>();
            foreach (DataRow row in dt.Rows)
            {
                T item = GetItem<T>(row);
                data.Add(item);
            }
            return data;
        }
        private static T GetItem<T>(DataRow dr)
        {
            Type temp = typeof(T);
            T obj = Activator.CreateInstance<T>();

            foreach (DataColumn column in dr.Table.Columns)
            {
                foreach (PropertyInfo pro in temp.GetProperties())
                {
                    if (pro.Name.ToLower() == column.ColumnName.ToLower())
                        pro.SetValue(obj, dr[column.ColumnName], null);
                    else
                        continue;
                }
            }
            return obj;
        }
    }
}


UserService.Cs:

This user service method have important methods the Authenticate Method will check the user name and password and generate the Jwt Token for the valid credentials. The Register method will get the user collection from the Controller and create Parameter Collection and pass it to the Register method in the Interconnectivity to register the user.

using JWTTokenAuthentication.Entities;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Data;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

namespace JWTTokenAuthentication.Services
{
    public interface IUserService
    {
        User Authenticate(string username, string password);
        string Register(User user);
        IEnumerable<User> GetAll();
    }

    public class UserService: IUserService
    {
        //private List<User> users = new List<User>
        //{
        //    new User{id=1,Firstname="Test",Lastname="User",Username="test",Password="test"}
        //};

        private List<User> users;
        DBConnectService connectService;

        private readonly AppSettings _appSettings;

        public UserService(IOptions<AppSettings> appSettings)
        {
            _appSettings = appSettings.Value;
            connectService = new DBConnectService(_appSettings.DBString);
            users = connectService.GetUsers();
        }

        public User Authenticate(string username,string password)
        {
            var user = users.SingleOrDefault(x => x.Username == username && x.Password == password);

            //return null if user not found
            if (user == null)
                return null;

            //authentication successfull so generate jwt token
            var tokenhandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new Claim[]
                {
                    new Claim(ClaimTypes.Name,user.id.ToString())
                }),
                Expires = DateTime.UtcNow.AddDays(7),
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),SecurityAlgorithms.HmacSha256Signature)
            };
            var token = tokenhandler.CreateToken(tokenDescriptor);
            user.Token = tokenhandler.WriteToken(token);

            //remove the password before returning null
            user.Password = null;

            return user;
        }

        public string Register(User user)
        {
            SqlParameter[] sqlParameters = new SqlParameter[6];

            sqlParameters[0] = new SqlParameter();
            sqlParameters[0].SqlDbType = SqlDbType.NVarChar;
            sqlParameters[0].ParameterName = "@cfirstname";
            sqlParameters[0].Value = user.Firstname;

            sqlParameters[1] = new SqlParameter();
            sqlParameters[1].SqlDbType = SqlDbType.NVarChar;
            sqlParameters[1].ParameterName = "@clastname";
            sqlParameters[1].Value = user.Lastname;

            sqlParameters[2] = new SqlParameter();
            sqlParameters[2].SqlDbType = SqlDbType.NVarChar;
            sqlParameters[2].ParameterName = "@cusername";
            sqlParameters[2].Value = user.Username;

            sqlParameters[3] = new SqlParameter();
            sqlParameters[3].SqlDbType = SqlDbType.NVarChar;
            sqlParameters[3].ParameterName = "@cpassword";
            sqlParameters[3].Value = user.Password;

            sqlParameters[4] = new SqlParameter();
            sqlParameters[4].SqlDbType = SqlDbType.NVarChar;
            sqlParameters[4].ParameterName = "@cemailid";
            sqlParameters[4].Value = user.Password;

            sqlParameters[5] = new SqlParameter();
            sqlParameters[5].SqlDbType = SqlDbType.TinyInt;
            sqlParameters[5].ParameterName = "@cstatus";
            sqlParameters[5].Value = 1;

            return connectService.RegisterUser(sqlParameters, "USP_USERREGISTRATION");
        }

        public IEnumerable<User> GetAll()
        {
            return users.Select(x => {
                x.Password = null;
                return x;
            });
        }
    }
}


 AppSettings.Json

This file holds the important key value pairs for this project.

{
"AppSettings": {
"Secret": "[Secret key need to generate the token]",
"DBString": "Server=DESKTOP\\SQLEXPRESS;Database=HRMSDB;User ID=[userid];Password=[password]"
},

"Logging": 
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

Startup.cs

The startup file have method to validate the token on the webapi request.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using JWTTokenAuthentication.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;

namespace JWTTokenAuthentication
{
    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)
        {
            //Enable Cross origin for webapi
            services.AddCors();
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
           
            //configure strongly typed settings objects
            var appSettingsSection = Configuration.GetSection("AppSettings");
            services.Configure<AppSettings>(appSettingsSection);

            // configure jwt authentication
            var appSettings = appSettingsSection.Get<AppSettings>();
            var key = Encoding.ASCII.GetBytes(appSettings.Secret);
            services.AddAuthentication(x =>
            {
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(x =>
            {
                x.RequireHttpsMetadata = false;
                x.SaveToken = true;
                x.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(key),
                    ValidateIssuer = false,
                    ValidateAudience = false
                };
            });

            services.AddScoped<IUserService, UserService>();
            services.AddControllers();
        }


        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();
            });
        }
    }
}



No comments:

Post a Comment