Background:
Back at work, I'm working on a Web Forms App that uses default, sliding expiration for Session and FormsAuthentication time outs. My task is to track user inactivity and , if needed, display modal window with a warning about session expiration, timer and a button to refresh session time out (it would refresh forms authentication time out also). Button is wired to the event on the master page with an empty body, it does the job for now. (I'm aware of other solutions).
Partially working solution:
My approach was to use master page Page_PreRender method to get Session and FormsAuthentication time out values, compare them and pass smaller(or any) value to the client's browser (with other necessary data). In the browser, I would then display warning with the timer at some particular moment in modal window and it would all work as intended... that is...when the user is doing nothing.
Problem:
Our app has lots of pages with update panels. Those update panels are used to interact with and manipulate data stored in the Session. Whenever user does something inside one of those update panels, the session time out slides (restarts). I cannot track those things on the client (in the real world), I need a way to track Session expiration on the server.
Things I don't want to do
I dont' want go through every single page, every single gridview, every single imagebutton etc and to bind some client event to track user actions. I want more... generic and maintainable solution if possible.
My idea:
I would like to be able to track/check Session or FormsAuthentication remaining time before expiration occurs OR to be notified if those values have been reseted. That way I could have initial data on the client about the moment in time when I should display the modal window with warning, and at that time, I would like to be able to check with the server first IF that time is right (approximately) or if I should update my javascript data on the client and prolong the time before displaying warning window since the session expiration has been updated...
And what about intercepting and filtering user requests ? Would that help ?
Have you considered Session State via SQL Server? It allows you to manage sessions via the database, opening up the option to query the expiration information you desire. Check out this link. Or this link.
Implement IHttpModule, attach to appropriate HttpApplication events and in its do what you want.
Although it doesn't do exactly what you're asking for, perhaps this Timeout control might at least provide some inspiration?
*Update: There's some comments on the blog from a guy implementing the control with sliding expirations and how he made it work.
First of all, thank you all for your suggestions. I've managed to solve this problem last week, just didn't had time to post earlier... and frankly, I was interested in other solutions. I'll make sure to read all the material you posted and suggested. Thank you for that.
Here's what I did:
On the server side:
I used cookies to track session expiration time. One thing that slipped my mind was that Page goes through its whole life cycle even on asynchronous requests. The whole idea was to use Master Page's Pre_Render event to set the appropriate cookies on EVERY request.
I was using UTC time format and was calculating milliseconds that are translatable to javascript time format :
var tmpDate = new DateTime(1970, 1, 1);
var nowUtc = DateTime.UtcNow;
var tmpSpan = new TimeSpan(nowUtc.Ticks - tmpDate.Ticks);
return tmpSpan.TotalMilliseconds;
With this, I had everything I needed for my calculations, current time, session expiration (Session.Timeout) time, and time for warning... basic math from there.
On the client side:
First, I red all three cookies to start tracking up and initialized two separate variables for tracking current server time. They were both initialized to the same value of the first cookie read of the current server time.
Then, I've used setTimeout function to read cookie (current server time) every second (jQuery plug-in). I was checking if current server time from former cookie read and current server time from latest cookie read were equal(separate variables) - that way I knew if there were any requests made and if there were, I would read 2 other cookies for warning time and time-out time to update things up and close warning dialog if it was opened. After that I would make current server time variables equal and continue comparing them after every cookie read and updating only one of them.
Also, on every setTimeout call I'm getting current time with
new Date().getTime();
which returns current UTC time measured on the client machine. In any case we rely on the presumption that client has valid clock set-up on his/her machine. After that I'm comparing current time with warning time and if current time is "bigger" that the warning time and "smaller" than the time-out time, then we have to display popup.
If the current time is "bigger" than the time-out time, click invisible button that calls method in the masters page code behind that logs out user. This works great even when user has multiple tabs open in browser. Once the user is logged out in one tab, every other page(tab) that tries to log him out also, gets redirected to login page.
If the user click OK button to confirm his/her presence, an empty event in masters page code behind is triggered (click hidden asp button). This is enough to refresh the session. This will automatically resend cookies, and everything goes as intended. (given that you have sliding session).
It was easy to implement timer in the warning window. I've dynamically created div for modal window with message and 3 spans. On every setTimeout iteration I was setting timer like this:
Calculate Hours, Minutes, Seconds from current javascript time
Place result in variable
If the result is smaller than 10 then prepend 0 (zero)
Target appropriate span by class(had problems with ID in IE 8, used unique classes) and set its text property to the calculated value $(obj).text(calculatedValue)
For The End
Make sure that, when you are reading cookies to parse them as floats, just to be sure.
I'm interested to see the application of server push technology and usage of web sockets in this particular situation but I think it would be an overkill if you would track and calculate session expiration on the server for every client.
I know that I've asked for session expiration tracking on the server and that this looks like tracking on the client, but it's not... those cookies are all we need for tracking and they come from server so... that's about it.
Here is my solution.
Ensure that FormsAuthentication.CookieMode is set to use cookies (it is by default).
Add new key to Session object and populate it in each page request (master-page's events like Page_Init, Page_Load etc.), include postbacks and callbacks.
Session["SessionUserLastPageRequestTime"] = MyApp.CommonUtils.GetServerDateTime();
Create a HttpHandler, which implements IReadOnlySessionState interface.
public class SessionStateHandler : IHttpHandler, IReadOnlySessionState
Place the SessionStateHandler haldler into folder with allowed anonymous access (or just put it to root folder).
public void ProcessRequest(HttpContext context)
{
string requestData, alertMessage;
using (var reader = new StreamReader(context.Request.InputStream))
{
requestData = reader.ReadToEnd();
}
/* -1: unauthorized;
* -2: authorized, but no information about last request time;
* -3: more than half session timeout, or not needed to inform user about session expiration;
* >=0: lesser than half session timeout or if needed to inform user. The number reflects the value of total amount of minutes to session expiration.
*/
int status = -1, idleTimeInMinutes = 0;
if (context.User?.Identity == null
|| !context.User.Identity.IsAuthenticated
|| context.Session == null
|| context.Session["SessionUserLastPageRequestTime"] == null)
{
status = -1;
alertMessage = "Session expired!";
}
else
{
DateTime now = MyApp.CommonUtils.GetServerDateTime();
var userLastPageRequest = (DateTime?)context.Session["SessionUserLastPageRequestTime"];
if (userLastPageRequest == null)
{
status = -2;
alertMessage = null;
}
else
{
var halfTimeout = TimeSpan.FromMinutes(context.Session.Timeout / 2);
TimeSpan idleTime = now.Subtract(userLastPageRequest.Value);
idleTimeInMinutes = (int)idleTime.TotalMinutes;
if (idleTime <= halfTimeout)
{
status = -3;
alertMessage = null;
}
else
{
status = context.Session.Timeout - idleTimeInMinutes;
/* Send time to session expiration only if 3 or lesser minutes remain */
if (status > 3)
status = -3;
/* If session expiration time's expanding (sliding expiration) has been done in some way */
else if (status < 0)
status = 0;
alertMessage = string.Format("Session Will Expire In {0} Minutes", status);
}
}
}
context.Response.Write(JsonConvert.SerializeObject(new { Status = status, Message = alertMessage }));
/* Remove new cookie to prevent sliding of session expiration time */
if (requestData == "TIMEOUT_REQUEST")
context.Response.Cookies.Remove(FormsAuthentication.FormsCookieName); /* by fact FormsAuthentication.FormsCookieName equals to ".ASPXAUTH" */
}
Note that server will slide session expiration only if request is detected after at least half of session timeout time is gone since session start, e.g. if Session.Timeout is set to 30min, new cookie with ticket will be send to client in response on first request after 15min since session start.
So as far as you want to KNOW the session expiration time, and not to EXTEND it instantly, then you need to remove new ASP.NET auth. cookie from response (look at line context.Response.Cookies.Remove(FormsAuthentication.FormsCookieName);).
Create continious request from client side by using setInterval or setTimer
var stc_ContiniousRequestOfSessionState_IntervalId;
function stc_ContiniousRequestOfSessionState() {
stc_ContiniousRequestOfSessionState_IntervalId = setInterval(stc_ContiniousRequestOfSessionState_OnInterval, 20000);
}
function stc_ContiniousRequestOfSessionState_OnInterval() {
stc_RequestSessionStateFromHttpHandler("TIMEOUT_REQUEST");
}
function stc_RequestSessionStateFromHttpHandler(dataToSend) {
var url = '<%: Page.ResolveClientUrl("~/handlers/SessionStateHandler.ashx") %>';
$.ajax({
type: "POST",
url: url,
contentType: "application/json",
dataType: "json",
data: dataToSend,
success: function (result) {
stc_sessionState = result.Status;
if (stc_sessionState == -2 || stc_sessionState == -3) {
hideSessionEndWarningMessage();
}
else if (stc_sessionState == -1) {
sessionExpired = true;
clearInterval(stc_ContiniousRequestOfSessionState_IntervalId);
showSessionTimeoutWarningMessage(result.Message);
}
else if (stc_sessionState >= 0) {
showSessionTimeoutWarningMessage(result.Message);
}
},
error: function (result) {
/* we can increase request frequency, re-send session request instantly or do something else at this place */
}
});
}
run JS function on page load by adding it to startup scripts on master-page.
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
if (Page.User.Identity.IsAuthenticated)
{
ScriptManager.RegisterStartupScript(this, this.GetType(), "test1", "continiousRequestOfSessionState();", true);
}
}
}
Related
Is there a way regarding ASP.NET Webforms to set the time to live for a session variable to be 5 minutes?
For instance if I have the variable:
Session["Name"] = "Bob";
After 5 minutes I'd like it to be destroyed, or at least be able to nullify it.
Example:
Session["Name"] = null;
//But in the background and not code-controlled as it has to be live over multiple pages.
I know that these Session variables expire upon timeout which is controlled within IIS, but I'm trying to expire them before the timeout occurs at intervals of 5 minutes, is this possible?
Create two session values (or a single object which contains the two values, which I'd likely prefer but for simplicity let's just use two values to illustrate), one with the value and one with its expiry time.
Session["Name"] = "Bob";
Session["NameExpiry"] = DateTime.UtcNow.AddMinutes(5);
Then wherever you use that value, check its expiry:
if (Session["NameExpiry"] != null && DateTime.Parse(Session["NameExpiry"]) < DateTime.UtcNow)
{
Session["Name"] = null;
Session["NameExpiry"] = null;
}
You could throw in a variety of error checking, other logic on the values, etc. But the principal is the same. Essentially, don't think of it as trying to set some kind of background timer to actively expire the session data, think of storing the expiry time as part of the session data and silently expiring it the next time you use it after that time stamp.
Since sessions are stored on the server side. create an action that can be called after 5 minutes from the client side.
public class ControllerName
{
public ActionResult ClearSession()
{
Session = null;
}
}
Then on the client-side:
<script>
setTimeout(function(){ $.ajax('/ControllerName/ClearSession'); }, 300000);
</script>
After OP mentioned:
The issue I have with this is that it's client-side controlled. I need it to work in the case of users that are preventing JS from running on pages
Session["Name"] = "Bob";
//task that will run after the specified interval
Task.Delay(300000).ContinueWith((task) => {
Session.Remove(Name);
});
Setup ASP.NET Core with Reactjs + Redux + Saga. It needs to notify the user when asp.net core session is expired. But the problem is by sending GET requests to check the session status we extend the session which means the session will not ever be over unless the tab in the browser will be closed(then GET requests won't be sent). This is the session setup Startup.cs
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(60 * 60);
options.Cookie.HttpOnly = true;
});
And then we send every 5 minute request from client to get identity:
function* checkSessionIsValid() {
try {
const response = yield call(axios.get, 'api/Customer/FetchIdentity');
if (!response.data) {
return yield put({
type: types.SESSION_EXPIRED,
});
yield delay(300000);
return yield put({ type: types.CHECK_SESSION_IS_VALID });
}
return;
} catch (error) {
return;
}
}
And the backend endpoint(_context is IHttpContextAccessor):
[HttpGet("FetchIdentity")]
public LoginInfo GetIdentity()
{
if (SessionExtension.GetString(_context.Session, "_emailLogin") != null)
{
return new LoginInfo()
{
LoggedInWith = "email",
LoggedIn = true,
Identity = ""
};
}
return null;
}
So we get session info from SessionExtension. But probably there is some way of getting it without connecting to the back end?
What you're asking isn't possible, and frankly doesn't make sense when you understand how sessions work. A session only exists in the context of a request. HTTP is a stateless protocol. Sessions essentially fake state by having the server and client pass around a "session id". When a server wants to establish a "session" with a client, it issues a session id to the client, usually via the Set-Cookie response header. The client then, will pass back this id on each subsequent request, usually via the Cookie request header. When the server receives this id, it looks up the corresponding session from the store, and then has access to whatever state was previously in play.
The point is that without a request first, the server doesn't know or care about what's going on with a particular session. The expiration part happens when the server next tries to look up the session. If it's been too long (the session expiration has passed), then it destroys the previous session and creates a new one. It doesn't actively monitor sessions to do anything at a particular time. And, since as you noted, sessions are sliding, each request within the expiration timeframe resets that timeframe. As a result, a session never expires as long as the client is actively using it.
Long and short, the only way to know the state of the session is to make a request with a session id, in order to prompt the server to attempt to restore that session. The best you can do if you want to track session expiration client-side is to set a timer client-side based on the known timeout. You then need to reset said time, with every further request.
var sessionTimeout = setTimeout(doSomething, 20 * 60 * 1000); // 20 minutes
Then, in your AJAX callbacks:
clearTimeout(sessionTimeout);
sessionTimeout = setTimeout(doSomething, 20 * 60 * 1000);
Perhaps the SessionState attribute can be utilized.
[SessionState(SessionStateBehavior.Disabled)]
https://msdn.microsoft.com/en-US/library/system.web.mvc.sessionstateattribute.aspx?cs-save-lang=1&cs-lang=cpp
EDIT: Realized that you're using .NET Core, not sure if the SessionState attribute or anything similar exist as of today.
I want to ask that can i logout user in Application_End???
,I know when Application_End invokes Application_End global.asax
But i just want to know that is it right to make all users logout in Application End, I just want to enter logout date time of user.
void Application_End(object sender, EventArgs e)
{
try
{
var LisLoginUsers = db.USER_LOGIN.Where(z => z.LOGOUT_DATETIME == null).ToList();
if (LisLoginUsers.Count != 0)
{
for (int i = 0; i < LisLoginUsers.Count; i++)
{
LisLoginUsers[i].LOGOUT_DATETIME = System.DateTime.Now;
db.SaveChanges();
}
}
}
catch (Exception msg)
{
ExceptionLogging.SendErrorToText(msg);
Response.Redirect("/Account/Error/");
}
}
No, you can't use Application_End to record singout in real sites.
Since Application_End event is not tied to any request there are some things you can't do - you can't act on particular user or redirect response somewhere (as there is no request/response to start with).
Indeed you can wipe out or bulk update information in the DB, but it will not achieve your actual goals to "enter logout date time of user".
Application shutdown is not tied to any user behavior - it only relates to stopping server side code for whatever reason (i.e. restarting the site due to update or inactivity). From user's point of view they still may be looking at pages and consider themselves "logged in".
In case you have non-trivial site with more than one server there is also no correlation when each server restarts IIS process making Application_End completely unfit for "user logged out" as one server can actively serve requests while other restarting.
Your best bet is to timeout user sessions either explicitly (i.e. 40 minutes from signing in) or with sliding expiration by either updating time on every request / heartbeat AJAX pings from the page.
How do I create an jquery script to automatically log out the user after a set period of inactivity? Or is there a jquery plugin that does this? It would need to do something like:
Create timer with setTimeout (eg for 30 mins)
Reset the timeout every time the user interacts with the page
After the setTimeout expires, use ajax to call the /logout action on the server (asp.net mvc)
Show a modal/lightbox dialog telling the user to login again
You have couple of different aspects that you need to consider here. First of all, what happens if the user just closes the browser or if the computer the person is using dies? If the user visits the page within 30 minutes, should the person still be logged in?
Let's say that the user should be logged in for 30 minutes, no matter what. The easiest way to start is to set a cookie timeout on an authentication cookie. Remember to update the timeout when each page refreshes. And use a jQuery timer to check if the cookie is still valid or not, or just keep track of the users login time.
So, jQuery timers, you could on each page load refresh the cookie and then just check if the timer/delay executes, if so, remove the cookie and display a modal box.
There's tons of ways of doing this, using the timers are one way.
$(this).oneTime(1800 , function() {
location.href='/logout'; // redirects to logout page.
});
Another aproach is to use server side checking for this, but you will not get the model box for this, as I said, there are tons of ways, it all depends on your preferences.
I know it's very old question, but could be useful for someone looking for similar solution to log out user if they are idle for certain timeperiod
http://www.dotnetfunda.com/articles/show/3130/automatically-logout-when-user-is-idle-for-sometime
The link may disappear so here is the code -
<script>
$(function () {
$("body").on('click keypress', function () {
ResetThisSession();
});
});
var timeInSecondsAfterSessionOut = 30; // change this to change session time out (in seconds).
var secondTick = 0;
function ResetThisSession() {
secondTick = 0;
}
function StartThisSessionTimer() {
secondTick++;
var timeLeft = ((timeInSecondsAfterSessionOut - secondTick) / 60).toFixed(0); // in minutes
timeLeft = timeInSecondsAfterSessionOut - secondTick; // override, we have 30 secs only
$("#spanTimeLeft").html(timeLeft);
if (secondTick > timeInSecondsAfterSessionOut) {
clearTimeout(tick);
window.location = "/Logout.aspx";
return;
}
tick = setTimeout("StartThisSessionTimer()", 1000);
}
StartThisSessionTimer();
I try to redirect my page for a logged in user with a certain amount of inactivity to a different page without killing the session.
I would like to substract the time I spent on the first page from the session, put the user on the new page, and then log the user out after his session (rest of the session time) times out and redirect the user to the login page.
I found this:
HttpContext.Current.Response.AppendHeader("Refresh", Convert.ToString(((HttpContext.Current.Session.Timeout * 2) - 5)) + "; Url=Dashboard.aspx");
but this interferes with my master page:
Context.Response.AppendHeader("Refresh",Convert.ToString((Session.Timeout * 60)) + "; URL=" + ResolveUrl("~/Logout.aspx"));
If it is easier, the user session does not need to be subtracted by the time the user spent on the first page.
Is there maybe an easy javascript out there that I missed on google?
Thanks,
Patrick
Ok, I did the following to solve this issue: Everytime a user hits a button, i call detime(), and also on the page_load I call detime(). Maybe not the best solution, but at least I got what I want ;)
function timer()
{
time1 = window.setTimeout('redirect();', 300000);
}
function redirect()
{
window.location = "XXX.aspx";
}
function detime()
{
if (time1 !=null) {
window.clearTimeout(time1);
time1 = null;
}
timer()
}
Well, I suppose it depends on your server platform, but in my experience it's the server that determines session timeout. The reload that your client-side code triggers will, unless you somehow override the normal behavior of the server, refresh the session and start the timer over again.
Now, what you could do is update the page client-side and not talk to the server at all. To do that, you'd effectively load the code to the "almost timed out" page at the same time you load the original page. Then, when your timeout fires, you just show the desired page and hide whatever's there.
I would encourage you to consider the usability issues with this overall scheme.
It seems like what you want to do is pass a timestamp along with the redirect and then capture the timestamp and either use that or, if its undefined, the current time as the time the user started. Then once that time has run out send the user to an explicit log out that kills their session.
As long as you redirect to a page that explicitly kills the session, I don't think you'll have a problem with expiration. Accept in the case where the user gets redirected to the new page, and then closes the browser, which maybe you could use an onunload script to kill the session explicitly again.