Session_End in Global.asax.cs not firing - c#

I have an Asp.net web application where I am using FormsAuthentication for User login.
I want to prevent multiple logins to the same user at the same time.
For this I have set the FormsAuthentication timeout to 15 minutes and Session.timeout to 15 minutes.
When the user closes the browser without logging out, or if the user is inactive for 15 minutes, it is not firing the Session_End() event in global.asax.cs file. I want to update the database field in the Session_End() event.
Code for Login:
if (Membership.ValidateUser(username, password))
{
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
1,
username,
DateTime.Now,
DateTime.Now.AddMinutes(15),
false,
FormsAuthentication.HashPasswordForStoringInConfigFile(password, "SHA1"));
// Now encrypt the ticket.
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
// Create a cookie and add the encrypted ticket to the cookie as data.
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
context.Response.Cookies.Add(authCookie);
context.Response.Redirect("/HomePage", false);
}
Global.asax.cs:
protected void Session_Start(Object sender, EventArgs e)
{
Session["init"] = 0;
Session.Timeout = 15;
}
protected void Session_End(Object sender, EventArgs e)
{
PersonObject person = new PersonObject();
// calling the function to update entry in database
person.ResetUserLoginStatus(HttpContext.Current.User.Identity.Name);
}
Function to update entry in database:
public bool ResetUserLoginStatus( string username="")
{
string sql = "UPDATE Person SET IsLogged=0 WHERE Person = #Person";
PersonObject person = new PersonObject();
object id = person.ExecuteScalar(sql, new Dictionary<string, object>() {
{ "Person", (!string.IsNullOrEmpty(username)?username:User.Name )}
}, "Person");
return true;
}
Web.config:
<authentication mode="Forms">
<forms loginUrl="/Security/Login.ashx/Home" name="SecurityCookie" timeout="15" slidingExpiration="true">
</forms>
</authentication>
<sessionState timeout="15" mode="InProc"></sessionState>
The problem is that when the browser is closed the ResetUserLoginStatus() method isn't called and I am unable to reset my value to 0. Since the field has not been reset to 0, that user won't be able to log in again.
Please suggest.

Session_End is actually not that useful or reliable. For one thing, it only fires at the end of the pipeline processing when an HTTP request has been received and a response has been rendered. That means it does NOT fire for a user who has simply closed their browser. Also, the event will never fire except for certain types of session state-- it won't work with State Server, for example, or SQL-based session state. The bottom line is you can't rely on it to maintain an unambiguous "Is logged in" flag.
Instead, I would store a "last page request received" time stamp. You can then infer the value of a "is logged in" flag; any user who has submitted a request in the past 15 minutes is still logged in.

Related

System.ArgumentException: Invalid value for 'encryptedTicket' parameter happens on any login after the first successful login

I am currently trying to replace our company wide user authentication that we use for all our internal web apps and what not as our current one was made in 2006 and fails on the regular. I was told to make it as simple as possible to implement on all existing projects. It is a .NET class library. It's .dll will be added as a reference to existing projects.
I am having an issue where I can log in exactly one time after all cookies have been cleared. Once I logout and log back in I get System.ArgumentException: Invalid value for 'encryptedTicket' parameter. I found some posts suggesting the cookie may be null, or I'm not trying to decrypt the name and not the value, but that wasn't the case. This happens on chrome and edge.
The user is authenticated every time though, assuming the correct username and password is used as I get redirected to the success page.
After authentication I add a cookie and then redirect.
private void AddCookie(int compID, bool persist, HttpContext httpContext)
{
httpContext.Request.Cookies.Add(SetUpSession(compID, persist));
FormsAuthentication.RedirectFromLoginPage(compID.ToString(), persist);
}
My method for creating the cookie
private HttpCookie SetUpSession(int companyID, bool persist)
{
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // ticket version
companyID.ToString(), // authenticated username
DateTime.Now, // issueDate
DateTime.Now.AddMinutes(30), // expiryDate
persist, // true to persist across browser sessions
FormsAuthentication.FormsCookiePath); // the path for the cookie
String encTick = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie("Cookie", encTick);
cookie.HttpOnly = true;
return cookie;
}
After I redirect to the success page there is a snipped of code that checks to see if the user is logged in. This is where the error happens
public dynamic isLoggedIn(HttpContext httpContext)
{
AuthenticationUtilities authUtil = new AuthenticationUtilities();
if (httpContext.Response.Cookies["Cookie"] != null)
{
companyID = authUtil.Authenticate(httpContext.Request.Cookies["Cookie"]);//the error occurs here
authUtil = new AuthenticationUtilities(companyID);
return authUtil;
}
else
{
httpContext.Response.Redirect("~/login.aspx");
return null;
}
}
The method that decrypts the cookie
public int Authenticate(HttpCookie cookie)
{
FormsAuthenticationTicket authTick = FormsAuthentication.Decrypt(cookie.Value);
return int.Parse(authTick.Name);
}
this method is called on any page that requires the user to be logged in, like this.
LMFJAuth.AuthenticationUtilities auth = _LMFJAuth.isLoggedIn(HttpContext.Current);//if the cookie is null it redirects to login.
This is the logout method
public void LogOut(HttpContext httpContext)
{
FormsAuthentication.SignOut();
HttpCookie cookie = new HttpCookie("Cookie");
cookie.Expires = DateTime.Now.AddMinutes(-1);
httpContext.Session.Clear();
httpContext.Response.Cookies.Add(cookie);
httpContext.Response.Redirect(FormsAuthentication.LoginUrl);
}
Can somone help explain what may be going on in which the value for the encrypted ticked is coming up as invalid after the first successful login/logout?
For me it was that the encrypted value of cookie.Value was coming up as greater than the maximum value of 4096, being 4200 in my case. I had just added some role strings to the user data.
I found it help to look up the source code of Microsoft classes when I'm stuck, in this case I used:
http://www.dotnetframework.org/default.aspx/DotNET/DotNET/8#0/untmp/whidbey/REDBITS/ndp/fx/src/xsp/System/Web/Security/FormsAuthentication#cs/1/FormsAuthentication#cs.

Set cookie value get lost after redirecting to page on the same app domain?

My web app based on.NET/Webforms does not work like expected because of losing value of set cookie after redirecting to page on the same app domain. But this app worked perfect before. The problem is as following:
Set cookie on Login:
protected void LogIn(object sender, EventArgs e)
{
if (IsValid)
{
. . .
string userData = JsonConvert.SerializeObject(eng);
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
1,
UserName.Text,
DateTime.Now,
DateTime.Now.AddMinutes(15),
false,
userData);
string encTicket = FormsAuthentication.Encrypt(authTicket);
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
Response.Cookies.Add(faCookie);
Response.Redirect("~/View_Id");
}
}
else
{
FailureText.Text = "Invalid username or password.";
ErrorMessage.Visible = true;
}
}
Setting a new cookie works fine, but the value get lost after redirecting. The value will be need on Global.FormsAuthentication_OnAuthenticate(Object sender, FormsAuthenticationEventArgs e).
ok fox, after reading about cookie i found out, that the set user data was too big (more than allowed 4k) - hence the set cookie value will be lost or overrided with null. Reducing the size of user data to the max. limit of 4k is the solution!

How to redirect user to specific page as Session Expire or abandon

I just make a Login page with session object. Storing some values in session (userId, userName, UserRoleId). As I know that default time of session is 20 mins. I want that if user watching a page and as session expiry happen then redirect him to login page. How it possible is there any event exists which fire automatically as specific session expire? so that I write logic there.
I search about global.asax then I use this but it is not working..
void Session_End(object sender, EventArgs e)
{
Response.Redirect("login.aspx");
}
On your master page class :
Stopwatch stopWatch = new Stopwatch();
On your master page page-load:
public Page_Load()
{
if (!page.ispostback)
{
System.Threading.Timer TimerForSessionExpire = new System.Threading.Timer(TickForSessionExpire, null, 0, 6000*60); // check after every 1 minute
}
else
{
stopWatch.reset();
stopWatch.start();
}
}
public void TickForSessionExpire()
{
if (stopWatch.Elapsed.TotalMinutes>20)
{
Response.Redirect("login.aspx");
}
}
I have not tested this but logic should be fine.
You'll probably have to do it at the client side using javascript. You can call an action which will check the status of your session and then redirect the user by setting a new location for your page.
If you want to drive it from the server, you can use Signalr and a hub. When the session expires, the hub call the client (still in js) and you will perform the redirection.
You could add <meta http-equiv="refresh" content="1205; url=http://domain/login.aspx"> to all your responses.
This redirect the page at 20 min plus 5 seconds to login.aspx.

Check authentication ticket expiration without affecting it

I am trying to implement a Web Application Project where my web pages can check the server for the Authentication ticket expiration date/time using AJAX.
I am using Forms Authentication with slidingExpiration.
The problem I run across is I can't figure out how to check the value without resetting it. I created a simple page - CheckExpiration.aspx - below is the code behind:
private class AjaxResponse
{
public bool success;
public string message;
public string expirationDateTime;
public string secondsRemaining;
public string issueDate;
}
protected void Page_Load(object sender, EventArgs e)
{
AjaxResponse ar = new AjaxResponse();
JavaScriptSerializer js = new JavaScriptSerializer();
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
string expiration = id.Ticket.Expiration.ToString();
TimeSpan timeRemaining = id.Ticket.Expiration - DateTime.Now;
ar.success = true;
ar.expirationDateTime = expiration;
ar.issueDate = id.Ticket.IssueDate.ToString();
ar.secondsRemaining = timeRemaining.Minutes.ToString() + ":" + timeRemaining.Seconds.ToString();
}
else
{
ar.success = false;
ar.message = "User not authenticated";
}
string output = js.Serialize(ar);
Response.Write(js.Serialize(ar));
}
I call this page from the Master page in my application using ajax every second. Past the halfway point in the authentication expiration, the expiration gets reset.
How do I prevent this behavior? Is there anything I can do in the header of the request maybe?
Why don't you store the expiration as a session variable that you compute yourself? You only need to get the value of id.Ticket.Expiration once. Then each call, get the value from the server and increment it accordingly, and store it back on the server.
http://msdn.microsoft.com/en-us/library/ms178581%28v=vs.85%29.aspx
Pseudocode:
if(!Session.KeyExists("Expiration"))
{
Session["Expiration"] = id.Ticket.Expiration;
}
Session["TimeRemaining"] = Session["Expiration"] - DateTime.Now;
// get all ajaxy here
Put your CheckExpiration.aspx page in its own application and deploy this as a virtual directory beneath your main application. In that virtual directory, configure slidingExpiration=false. Your code will work as-is but will not regenerate the ticket when it gets below half the time until expiration.
Here's what I did in a quick local project to verify that it works:
Created a new web application AuthTest4 and configured it to use local IIS server in path /AuthTest4
Went into IIS and changed the Machine Key setting for /AuthTest4 to uncheck all the AutoGenerate/Isolate options and generated its own MachineKey.
Created an empty web application ExpCheck and put your CheckExpiration.aspx code in it
Configured ExpCheck web application to use local IIS in the virtual directory /AuthTest4/ExpCheck
Modified the web.config of ExpCheck application to have only the section shown below
ExpCheck web.config. All other security settings will cascade down from the parent virtual directory.
<system.web>
<authentication mode="Forms">
<forms slidingExpiration="false" />
</authentication>
</system.web>

IsInRole problem

I'm working for the first time with Forms Authentication, I'm using an example from the web to learn, I included in my web.config
<authentication mode="Forms">
<forms name="MYWEBAPP.ASPXAUTH" loginUrl="Login.aspx" protection="All" path="/"/>
</authentication>
<authorization>
<deny users="?"/>
</authorization>
Then I created a page for logging in "login.aspx", and coded this on a button, just to start;
private void btnLogin_Click(Object sender, EventArgs e)
{
// Initialize FormsAuthentication
FormsAuthentication.Initialize();
// Create a new ticket used for authentication
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // Ticket version
Username.Value, // Username associated with ticket
DateTime.Now, // Date/time issued
DateTime.Now.AddMinutes(30), // Date/time to expire
true, // "true" for a persistent user cookie
"accountants, seekers, copiers, typers", // User-data, in this case the roles
FormsAuthentication.FormsCookiePath);// Path cookie valid for
// Encrypt the cookie using the machine key for secure transport
string hash = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(
FormsAuthentication.FormsCookieName, // Name of auth cookie
hash); // Hashed ticket
// Set the cookie's expiration time to the tickets expiration time
if (ticket.IsPersistent) cookie.Expires = ticket.Expiration;
// Add the cookie to the list for outgoing response
Response.Cookies.Add(cookie);
}
Also I coded in Global.asax;
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
if(HttpContext.Current.User != null)
{
if(HttpContext.Current.User.Identity.IsAuthenticated)
{
if (HttpContext.Current.User.Identity is FormsIdentity)
{
FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
FormsAuthenticationTicket ticket = id.Ticket;
// Get the stored user-data, in this case, our roles
string userData = ticket.UserData;
string[] roles = userData.Split(',');
HttpContext.Current.User = new GenericPrincipal(id, roles);
}
}
}
}
And finally in another page I tried to confirm the roles aquired;
protected void Page_Load(object sender, EventArgs e)
{
string str = null;
if (User.IsInRole("seekers"))
{
str += " seekers ";
}
if (User.IsInRole("accountants"))
{
str += " accountants ";
}
if (User.IsInRole("copiers"))
{
str += "copiers";
}
Response.Write(str);
}
But something strange happens cause it only writes "accountants" (note that "accountants" is the firts element in the delimited comma string) and not the other roles, which were supposed to be showed. I changed the order of the role list in the btnlogin click event writing "copiers" as the first element and it's written only "copiers" in the page.
I've tried with different combinations and always is printed the first element of the delimited comma string.
Sorry by my ignorance but what is happening here, are all the roles there or not? is normal? or there's something I'm forgetting here?
Thanks in advance.
Drop the spaces in
"accountants, seekers, copiers, typers"
Try it without the spaces after the commas:
"accountants,seekers,copiers,typers"
The Split will be creating strings like "accountants", " seekers", " copiers", " typers",
You're splitting on ',' ... but when you initialize your string of roles it's actually ", " (comma space).
A tip on this is to use debugger and use the immediate window to actually "see" what's happening as it happens.

Categories

Resources