ASP.NET session state and authentication

A few weeks after rebuilding a security implementation of an existing ASP.NET webforms system, I got a call from my client saying one of their customers lost their in-session data and was confronted with defaults from the system. A quick look at the logs showed the customer in this case had left the system idle for a while, then returned after a session timeout had occured. As expected the user was redirected to the logon screen, but managed somehow to get back into the system bypassing the logon (although of course not as a different user).

Explanation

After some research looking into the configuration I found the installation of a third-party component we used in the system had added a line to the web.config:

<sessionState timeout=”20” …….

As for the authentication, the system used ASP.NET forms authentication with the default timeout, which is 30 minutes. At this point it is important to realize that ASP.NET authentication is not connected to a particular session. ASP.NET configured for forms authentication creates an authentication ticket with a timeout that is usually stored in an authentication cookie (with default name “.ASPXAUTH”). Setting the timeout on the forms authentication does NOT set the session timeout, something that is often misunderstood or overlooked in ASP.NET applications.

Apparently the user in this case had a session timeout but after being redirected to the logon page used the browser’s back button BEFORE the authentication timeout occured. The difference between session state timeout and authentication timeout had left a 10 minute window where a user without a session was still authenticated. Since the user still had a valid authentication ticket, the system just created a new session but of course the previously stored session information was lost, presenting the user with default settings.

Synchronize session and authentication

To avoid the above situation from happening, first of all set the authentication timeout and the session timeout to at least the same values. By default authentication uses a sliding expiration unless configured not to, meaning the counter is reset on user activity (but not necessarily after each request). For session state this is always the case.

Depending on your requirements you can choose a strategy to avoid getting sessions out of sync with authentication. One way is to just reinitialize the session if it was expired and the user is still logged in. This is easy if no or little information is kept in relation to the session. Another way is to make sure session ending does end the authentication and vice versa.

Part 1: end authentication when session is expired

To implement ending the authentication after session expiration, first make sure the session sticks by entering something into it, otherwise the session will get renewed on every request. To do this, directly after authenticating the user store the session ID in a session variable. So in a logon form (ASP.NET webforms) or ASP.NET MVC controller it will look something like:

...
//Authentication, validation etc.
....

FormsAuthentication.SetAuthCookie(UserName, false);
Session["__MyAppSession"] = Session.SessionID;
..

Since we are going to bind our authentication to the session, it is pointless to set the createPersistentCookie parameter to true of course.

Now we can check on any request if we still have the session active, and if not log out the user. The exact place to do this can be tricky and causes a lot of questions on forums and such, but arguably the best location is in the Application_AcquireRequestState event in global.asax.

Since there’s no guarantee we have a valid user or session in this event, we need to do a lot of null-checking. The code will look like this:

void Application_AcquireRequestState(object sender, EventArgs e)
 {
 var session = System.Web.HttpContext.Current.Session;
 if (session == null || string.IsNullOrWhiteSpace(session.SessionID)) return;
 var userIsAuthenticated = User != null &&
 User.Identity != null &&
 User.Identity.IsAuthenticated;
 if (userIsAuthenticated && !session.SessionID.Equals(Session["__MyAppSession"]))
 {
 Logoff();
 }
 // part 2 gets here
 }
private void Logoff()
{ 
    FormsAuthentication.SignOut(); 
    var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, string.Empty) { Expires = DateTime.Now.AddYears(-1) }; 
    Response.Cookies.Add(authCookie); 
    FormsAuthentication.RedirectToLoginPage(); 
}

Now, if a request is sent while the session expired with the user still authenticated, the stored ID (or actually the session variable “__MyAppSession”) will no longer be present and the user will be logged of. The part with the cookie I will explain below.

Part 2: end session when authentication ends (timeout)

This can be added quite easily. After the comment in the above event code, add the following:

if (!userIsAuthenticated && session.SessionID.Equals(Session["__MyAppSession"]))
{ 
    ClearSession();
}

And the ClearSession method:

private void ClearSession()
{
    Session.Abandon();
    var sessionCookie = new HttpCookie("ASP.NET_SessionId", string.Empty) { Expires = DateTime.Now.AddYears(-1) };
    Response.Cookies.Add(sessionCookie);
}

Now if a session exists with session information while the user is no longer authenticated, the session will be abandoned.

Part 3: full user logout

When a user actively logs off we have to clear both the authentication and the session. Since this is usually a single “fire and forget” operation that can be called from various places it’s usually implemented best as a static operation in a logical place.

Per recommendation it is best not to only call FormsAuthentication.SignOut() and session.Abandon(), but to actively overwrite the cookies with ones having expired dates. So a full logoff will look like this:

public static void Logoff()
{
    FormsAuthentication.SignOut();
    Session.Abandon();
    var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, string.Empty) { Expires = DateTime.Now.AddYears(-1) };
    Response.Cookies.Add(authCookie);
    var sessionCookie = new HttpCookie("ASP.NET_SessionId", string.Empty) { Expires = DateTime.Now.AddYears(-1) };
    Response.Cookies.Add(sessionCookie);
    FormsAuthentication.RedirectToLoginPage();
}

In the above Session and Response are of course from the context (controller or form).

A note on ASP.NET MVC: per recommendation MVC applications should be stateless as much as possible, and not store information in the session. In this case it doesn’t really matter if the session gets renewed automatically while a user is still authenticated since there shouldn’t be any persistent information in there anyway.

1 thought on “ASP.NET session state and authentication

Leave a comment