Site icon Tutexchange

How to Implement Audit Trail In ASP.NET CORE

Advertisements

In this article, we are going to learn how to implement an Audit Trail in ASP.NET Core.

You can find the entire source code of the implementation with SQL Script Github Link

Before moving further, the question arises what Audit Trail is?

As per Wikipedia. An audit trail (also called audit log) is a security-relevant chronological record, set of records, and/or destination and source of records that provide documentary evidence of the sequence of activities that have affected at any time a specific operation, procedure, event, or device.

https://en.wikipedia.org/wiki/Audit_trail

Tracking User Activity on Your Website: A Simple Guide

In today’s digital age, tracking user activity on your website is crucial for both enhancing user experience and ensuring security. In this blog post, we’ll discuss how to log user activities after they log in to your application. This information can help you understand user behavior and trace security issues if they arise.

Why Track User Activity?

  1. Enhance User Experience: By tracking user activity, you can understand how users interact with your application. This insight allows you to improve navigation and usability.
  2. Security: Tracking activities helps in identifying suspicious behavior. If a user claims their account is hacked, you can trace the IP addresses and actions taken to verify the claim.

Example Scenario

Here’s a simple example to illustrate how user activity can be tracked:

  1. Login Event: When a user logs into your application, this action is logged as a login event.
  2. Navigation Event: After logging in, the user navigates to different pages within the application. Each page visit is logged.
  3. Action Event: If the user clicks on a specific menu, such as “Change Password,” this action is logged.
  4. Logout Event: Finally, when the user logs out of the application, this action is logged as well.

How to Implement User Activity Logging

To track user activities, you can implement an event logging system. Here’s a basic outline of how to do it:

  1. Create a Logging Function: Write a function that logs events with details such as timestamp, user ID, event type, and IP address.
  2. Log Events at Key Points: Call this logging function at key points in your application, such as during login, navigation, specific actions, and logout.

Table of Contents

Getting Started

We are going to create a new application, as shown below.

Next, we are going to set Project Name DemoAudit and location. In the last part, we will choose the .Net Core framework and ASP.NET Core Version 3.1 as the framework for application and few advanced settings for such as configuring HTTPS and enabling docker; we are not going to enable docker settings for this project.

Now finally, click on create button to create a project.

Project structure

The project structure generated according to the configuration.

After creating the project next, we are going to install the Dapper Package from NuGet Package.

Dapper – a simple object mapper for .Net
https://github.com/StackExchange/Dapper

Installing Dapper Package from NuGet Package

After adding Dapper next, we are going to add Model.

Adding Model

In the Model, we have added properties that we will log and insert into the database.

public class AuditModel
{
    public int AuditId { get; set; }
    public string Area { get; set; }
    public string ControllerName { get; set; }
    public string ActionName { get; set; }
    public string RoleId { get; set; }
    public string LangId { get; set; }
    public string IpAddress { get; set; }
    public string IsFirstLogin { get; set; }
    public string LoggedInAt { get; set; }
    public string LoggedOutAt { get; set; }
    public string LoginStatus { get; set; }
    public string PageAccessed { get; set; }
    public string SessionId { get; set; }
    public string UrlReferrer { get; set; }
    public string UserId { get; set; }
}

After adding the model next, we will add the Repository Folder to Add Interface and Concert class.

Adding Repository Folder

Inside the repository folder, I am going to add an interface and concrete class.

Adding Interface and Concrete Class in Repository Folder

In concrete class, I have written logic for inserting data into the database.

We have not created a table to store logs; we will generate it dynamically according to datewise because storing records in one table will cause a problem in querying it.

The logic for generating the table is written in the store procedure.

using System;
using System.Data;
using System.Data.SqlClient;
using Dapper;
using DemoAudit.Models;
using Microsoft.Extensions.Configuration;

namespace DemoAudit.Repository
{
    public class AuditRepository : IAuditRepository
    {
        private readonly IConfiguration _configuration;
        public AuditRepository(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        public void InsertAuditLogs(AuditModel objauditmodel)
        {
            using SqlConnection con = new SqlConnection(_configuration.GetConnectionString("AuditDatabaseConnection"));
            try
            {
                con.Open();
                var para = new DynamicParameters();
                para.Add("@UserID", objauditmodel.UserId);
                para.Add("@SessionID", objauditmodel.SessionId);
                para.Add("@IPAddress", objauditmodel.IpAddress);
                para.Add("@PageAccessed", objauditmodel.PageAccessed);
                para.Add("@LoggedInAt", objauditmodel.LoggedInAt);
                para.Add("@LoggedOutAt", objauditmodel.LoggedOutAt);
                para.Add("@LoginStatus", objauditmodel.LoginStatus);
                para.Add("@ControllerName", objauditmodel.ControllerName);
                para.Add("@ActionName", objauditmodel.ActionName);
                para.Add("@UrlReferrer", objauditmodel.UrlReferrer);
                para.Add("@Area", objauditmodel.Area);
                para.Add("@RoleId", objauditmodel.RoleId);
                para.Add("@LangId", objauditmodel.LangId);
                para.Add("@IsFirstLogin", objauditmodel.IsFirstLogin);
                con.Execute("Usp_InsertAuditLogs", para, null, 0, CommandType.StoredProcedure);
            }
            catch (Exception)
            {
                throw;
            }
        }
    }
}

Stored procedure used for Inserting and generating Dynamic table

After completing with Insert log’s part next, we need to plug it into the proper place for logging requests. For doing that, we need to create a filter that we can use.

Creating AuditFilter

We will create ActionFilterAttribute Attribute with the name AuditFilterAttribute in this filter. We will log all parameters and then pass them to the InsertAuditLogs method for saving into the database.

using System;
using DemoAudit.Helpers;
using DemoAudit.Models;
using DemoAudit.Repository;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Headers;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace DemoAudit.Filters
{
    public class AuditFilterAttribute : ActionFilterAttribute
    {
        private readonly IAuditRepository _auditRepository;
        private readonly IHttpContextAccessor _httpContextAccessor;
        public AuditFilterAttribute(IHttpContextAccessor httpContextAccessor, IAuditRepository auditRepository)
        {
            _httpContextAccessor = httpContextAccessor;
            _auditRepository = auditRepository;
        }
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var objaudit = new AuditModel(); // Getting Action Name 
            var controllerName = ((ControllerBase)filterContext.Controller)
                .ControllerContext.ActionDescriptor.ControllerName;
            var actionName = ((ControllerBase)filterContext.Controller)
                .ControllerContext.ActionDescriptor.ActionName;
            var actionDescriptorRouteValues = ((ControllerBase)filterContext.Controller)
                .ControllerContext.ActionDescriptor.RouteValues;

            if (actionDescriptorRouteValues.ContainsKey("area"))
            {
                var area = actionDescriptorRouteValues["area"];
                if (area != null)
                {
                    objaudit.Area = Convert.ToString(area);
                }
            }

            var request = filterContext.HttpContext.Request;

            if (!string.IsNullOrEmpty(Convert.ToString(filterContext.HttpContext.Session.GetInt32(AllSessionKeys.LangId))))
            {
                objaudit.LangId = Convert.ToString(filterContext.HttpContext.Session.GetInt32(AllSessionKeys.LangId));
            }
            else
            {
                objaudit.LangId = "";
            }

            if (!string.IsNullOrEmpty(Convert.ToString(filterContext.HttpContext.Session.GetInt32(AllSessionKeys.UserId))))
            {
                objaudit.UserId = Convert.ToString(filterContext.HttpContext.Session.GetInt32(AllSessionKeys.UserId));
            }
            else
            {
                objaudit.UserId = "";
            }

            if (!string.IsNullOrEmpty(Convert.ToString(filterContext.HttpContext.Session.GetInt32(AllSessionKeys.RoleId))))
            {
                objaudit.RoleId = Convert.ToString(filterContext.HttpContext.Session.GetInt32(AllSessionKeys.RoleId));
            }
            else
            {
                objaudit.RoleId = "";
            }


            if (!string.IsNullOrEmpty(Convert.ToString(filterContext.HttpContext.Session.GetString(AllSessionKeys.IsFirstLogin))))
            {
                objaudit.IsFirstLogin = Convert.ToString(filterContext.HttpContext.Session.GetString(AllSessionKeys.IsFirstLogin));
            }
            else
            {
                objaudit.IsFirstLogin = "";
            }

            objaudit.SessionId = filterContext.HttpContext.Session.Id; ; // Application SessionID // User IPAddress 
            if (_httpContextAccessor.HttpContext != null)
                objaudit.IpAddress = Convert.ToString(_httpContextAccessor.HttpContext.Connection.RemoteIpAddress);

            objaudit.PageAccessed = Convert.ToString(filterContext.HttpContext.Request.Path); // URL User Requested 

            objaudit.LoginStatus = "A";
            objaudit.ControllerName = controllerName; // ControllerName 
            objaudit.ActionName = actionName;

            RequestHeaders header = request.GetTypedHeaders();
            Uri uriReferer = header.Referer;

            if (uriReferer != null)
            {
                objaudit.UrlReferrer = header.Referer.AbsoluteUri;
            }

            _auditRepository.InsertAuditLogs(objaudit);
        }
    }
}

Now we have created AuditFilter. Next, we need to plug it in the proper place.

Plugging AuditFilter to Log Activity

Either we can register filter globally for logging all request or on the specific controller.

  1. Globally
  2. Controller wise

Filter scope Globally

For logging Globally, we need to plug it in startup class.

public void ConfigureServices(IServiceCollection services)
 {
     services.AddControllersWithViews(options =>
     {
         options.Filters.Add(typeof(AuditFilterAttribute));
     });
 }

Filter scope Controller

To apply a filter on the controller with a dependency, you need to register it in the ConfigureServices method, as shown below.

public void ConfigureServices(IServiceCollection services)
 {
    services.AddScoped<AuditFilterAttribute>();
 }

Applied filter on Dashboard Controller

using DemoAudit.Filters;
using Microsoft.AspNetCore.Mvc;

namespace DemoAudit.Controllers
{
    [ServiceFilter(typeof(AuditFilterAttribute))]
    public class DashboardController : Controller
    {
        public IActionResult Dashboard()
        {
            return View();
        }
    }
}

After plugging in Filter to log next, I have added a few controller and view for the logging demo, added Portal Controller with login action method and Dashboard Controller with Dashboard action method.

Logging Login Activity

To log in User Login, I have created the AuditLogin Method, which is called on Login Controller POST method.

private void AuditLogin()
        {
            var objaudit = new AuditModel();
            objaudit.RoleId = Convert.ToString(HttpContext.Session.GetInt32(AllSessionKeys.RoleId));
            objaudit.ControllerName = "Portal";
            objaudit.ActionName = "Login";
            objaudit.Area = "";
            objaudit.LoggedInAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
            if (_httpContextAccessor.HttpContext != null)
                objaudit.IpAddress = Convert.ToString(_httpContextAccessor.HttpContext.Connection.RemoteIpAddress);
            objaudit.UserId = Convert.ToString(HttpContext.Session.GetInt32(AllSessionKeys.UserId));
            objaudit.PageAccessed = "";
            objaudit.UrlReferrer = "";
            objaudit.SessionId = HttpContext.Session.Id;
            _auditRepository.InsertAuditLogs(objaudit);
        }

Calling AuditLogin Method While User Login

[HttpPost]
  [ValidateAntiForgeryToken]
  public IActionResult Login(LoginViewModel loginViewModel)
  {
      if (loginViewModel.Username =="demoaudit" && loginViewModel.Password == "demoaudit")
      {

          HttpContext.Session.SetString(AllSessionKeys.UserId, "1");
          HttpContext.Session.SetString(AllSessionKeys.UserName, "demoaudit");
          HttpContext.Session.SetString(AllSessionKeys.IsFirstLogin, "N");
          HttpContext.Session.SetInt32(AllSessionKeys.RoleId, 1);
          HttpContext.Session.SetInt32(AllSessionKeys.LangId, 1);
          AuditLogin();
          return RedirectToAction("Dashboard", "Dashboard");
      }
      return View(loginViewModel);
  }

Logging Logout Activity

I have created the AuditLogout method to Log the Logout activity called when the logout action method is called.

private void AuditLogout()
        {
            var objaudit = new AuditModel();
            objaudit.RoleId = Convert.ToString(HttpContext.Session.GetInt32(AllSessionKeys.RoleId));
            objaudit.ControllerName = "Portal";
            objaudit.ActionName = "Logout";
            objaudit.Area = "";
            objaudit.LoggedOutAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
            if (_httpContextAccessor.HttpContext != null)
                objaudit.IpAddress = Convert.ToString(_httpContextAccessor.HttpContext.Connection.RemoteIpAddress);
            objaudit.UserId = Convert.ToString(HttpContext.Session.GetInt32(AllSessionKeys.UserId));
            objaudit.PageAccessed = "";
            objaudit.UrlReferrer = "";
            objaudit.SessionId = HttpContext.Session.Id;
       
            _auditRepository.InsertAuditLogs(objaudit);
        }

Calling AuditLogout Method While the User is logging out

 [HttpGet]
 public IActionResult Logout()
 {
     try
     {
         HttpContext.Session.Clear();
         AuditLogout();
         return RedirectToAction("Login", "Portal");
     }
     catch (Exception)
     {
         throw;
     }
 }

Database and Table where Logs are Stored

We have created AuditDB Database and executed the Usp_InsertAuditLogs stored procedure. When a first-time stored procedure is called, it will check the current day table in the database; if not, it will create and start inserting data.

The User Activity is stored in the table. If you IPAddress field, then it shows because we are working on localhost. If you host the application, then it will start logging the IPAddress of Accessing Users.

You can find the entire source code of the implementation with SQL Script Github Link

Exit mobile version