Introduction
In modern web applications, ensuring that user sessions are managed securely and consistently across multiple tabs is crucial. This tutorial focuses on implementing session auto logout across multiple tabs in a .NET Core application. By following this guide, you'll learn how to synchronize logout actions, so when a user logs out from one tab, all other open tabs automatically reflect this change. This not only enhances security but also improves the user experience by preventing any inconsistencies in session state across different tabs. Whether you are developing single-page applications or traditional web apps, these techniques will help you maintain robust session management in your .NET Core applications.
Step 1. Create a Modal class as below
public class Session
{
public int? UserId { get; set; }
public string Sessionid { get; set; }
}
Step 2. In the Login post method capture the session ID and user Login ID below
#region--------------------------Insert Session into DB---------------
HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
Session obj = new Session();
obj.UserId = loggedInUser.UserId;
obj.Sessionid = HttpContext.Session.Id;
var SaveSession = _commonRepository.SessionInsert(obj).Result;
#endregion-------------------------------------------------------------
Step 3. Create an Interface and add the below code
Task<CustomerResult> SessionInsert(Session customer);
Task<CustomerResult> DeleteSessionId(string sessionid, int userid);
Task<IEnumerable<Session>> GetSessionExist(string sessionid, int userid);
Step 4. Create a Repository and add the below code
public Task<CustomerResult> SessionInsert(Session customer)
{
DynamicParameters param = new DynamicParameters();
object[] objArray = new object[] {
"ACTION","A",
"UserId",customer.UserId,
"Session",customer.Sessionid
};
param = objArray.ToDynamicParameters("PAR_OUT");
var result = Connection.Query<string>("USP_M_SESSIONSTORE", param, commandType: System.Data.CommandType.StoredProcedure);
string response = param.Get<string>("PAR_OUT");
CustomerResult customerResult = new CustomerResult() { Remark = response };
return Task.FromResult(customerResult);
}
public Task<CustomerResult> DeleteSessionId(string sessionid, int userid)
{
DynamicParameters param = new DynamicParameters();
object[] objArray = new object[] {
"ACTION","B",
"Session",sessionid,
"UserId",userid
};
param = objArray.ToDynamicParameters("PAR_OUT");
var result = Connection.Query<string>("USP_M_SESSIONSTORE", param, commandType: System.Data.CommandType.StoredProcedure);
string response = param.Get<string>("PAR_OUT");
CustomerResult customerResult = new CustomerResult() { Remark = response };
return Task.FromResult(customerResult);
}
public async Task<IEnumerable<Session>> GetSessionExist(string session, int userid)
{
DynamicParameters _params = new DynamicParameters();
_params.Add("ACTION", "C");
_params.Add("Session", session);
_params.Add("UserId", userid);
var result = await Connection.QueryAsync<Session>("USP_M_SESSIONSTORE", _params, commandType: CommandType.StoredProcedure);
return result;
}
Step 5. Tables and Procedure code
CREATE TABLE [dbo].[M_SESSION](
[Id] [int] IDENTITY(1,1) NOT NULL,
[UserId] [int] NULL,
[SessionId] [varchar](50) NULL,
[deletedflag] [bit] NULL,
[CreatedOn] [datetime] NULL,
CONSTRAINT [PK_SessionTable] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE TABLE [dbo].[T_Transaction_Error](
[intErrorId] [int] IDENTITY(1,1) NOT NULL,
[vchErrorMessage] [varchar](512) NULL,
[dtmErrorDate] [datetime] NULL,
[vchProcedureName] [varchar](80) NULL,
[vchLineNumber] [varchar](250) NULL,
[vchErrorProcedure] [varchar](512) NULL,
CONSTRAINT [PK_T_Transaction_Error] PRIMARY KEY CLUSTERED
(
[intErrorId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
) ON [PRIMARY]
CREATE PROCEDURE [dbo].[USP_M_SESSIONSTORE]
(
@UserId INT=NULL,
@Session VARCHAR(50)='',
@ACTION CHAR(1)=NULL,
@PAR_OUT VARCHAR(24)=NULL OUTPUT
)
AS
BEGIN
BEGIN TRY
BEGIN TRAN
SET NOCOUNT ON;
SET XACT_ABORT ON;
IF @ACTION='A'
BEGIN
INSERT INTO M_SESSION
(
UserId,
SessionId,
CreatedOn,
deletedflag
)
VALUES
(
@UserId,
@Session,
GETDATE(),
0
)
SET @PAR_OUT='1';
END
ELSE IF @ACTION='B'
BEGIN
/*--update SessionTable set deletedflag=1 where UserId=@UserId*/
DELETE M_SESSION WHERE UserId=@UserId
SET @PAR_OUT='2';
END
ELSE IF @ACTION='C'
BEGIN
select *
from M_SESSION
where SessionId=@Session
and deletedflag=0
and UserId=@UserId
END
COMMIT
END TRY
BEGIN CATCH
IF(@@TRANCOUNT>0)
BEGIN
ROLLBACK;
END
INSERT INTO T_TRANSACTION_ERROR
(
VCHERRORMESSAGE
,DTMERRORDATE
,VCHPROCEDURENAME
,[vchLineNumber]
)
VALUES
(
ERROR_MESSAGE()
,GETDATE()
,'SessionStore'
,ERROR_LINE()
);
SET @PAR_OUT='0';
END CATCH
END;
Step 6. In the Logout method add the below code
[HttpGet]
public IActionResult Logout()
{
LoggedInUser profile = HttpContext.Session.Get<LoggedInUser>(KeyHelper.UserKey);
if (profile != null)
{
var DeleteSession = _commonRepository.DeleteSessionId(HttpContext.Session.Id, profile.UserId);
}
return RedirectToAction("Index", "Website");
}
Step 7. Add the CheckSession method in the account controller
public IActionResult CheckSession()
{
LoggedInUser profile = HttpContext.Session.Get<LoggedInUser>(KeyHelper.UserKey);
if (profile != null)
{
var sessionExists = _commonRepository.GetSessionExist(HttpContext.Session.Id, profile.UserId).Result;
if (sessionExists.Count() != 0)
{
return Ok("valid");
}
else
{
return Ok("expired");
}
}
else
{
return RedirectToAction("Logout", "Account");
}
}
Step 8. In the layout page add the below JavaScript code
<script type="text/javascript">
(function poll() {
setTimeout(function () {
$.ajax({
url: '/Account/CheckSession',
success: function (data) {
if (data === 'expired') {
// Session expired, perform logout action
window.location.href = "/Account/Logout";
} else {
// Session still valid, continue polling
poll();
}
},
error: function (xhr, status, error) {
// Handle error
console.error(error);
}
});
}, 8000); // Poll every 5 seconds
})();
</script>
Conclusion
Implementing session auto logout across multiple tabs in .NET Core is crucial for enhancing security and protecting sensitive user data. By ensuring that sessions expire after a specified period of inactivity, you mitigate the risk of unauthorized access and potential data breaches.
Throughout this guide, we have explored various techniques to achieve this functionality, such as utilizing session expiration policies, leveraging client-side scripting, and employing server-side mechanisms to track user activity. Each approach offers its advantages and considerations, allowing developers to choose the most suitable method based on their project requirements and constraints.
By incorporating session auto logout functionality into your .NET Core applications, you not only enhance security but also provide a seamless and user-friendly experience. Users can confidently interact with your application across multiple tabs, knowing that their sessions will be terminated after a period of inactivity, safeguarding their privacy and sensitive information.
In conclusion, implementing session auto logout across multiple tabs in .NET Core is an essential aspect of developing secure and robust web applications. By following best practices and leveraging appropriate techniques, developers can effectively manage user sessions and mitigate the risk of unauthorized access, thereby enhancing the overall security posture of their applications.