4

Is there a way to be notified when a user becomes logged out with an ASP.net website?

Note: A user can become logged out without visiting or clicking a "logout" link.

When a user is logged out i want to fetch clear some session related information, and write to a database.

Note: There is the LoginStatus.OnLoggedOut event. Problem is that that control doesn't exist on every page, nor does the user have to use a LoginStatus control in order to logout (e.g.when the login times out, or the user is logged out)

In the same way that Global.asax has a global On Session Stop notification:

void Session_End(object sender, EventArgs e) 
{
}

i assume somewhere there's a On User Logged Out notifiation:

void LoggedOut(object sender, EventArgs e)
{
}

Bonus Reading

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219

3 Answers3

2

I disagree. There are times when you would want to know when a user logs out. For example I have an application that needs to scan all logged in users to see if there is "anyone home" at a management level. At times a manager might be surfing the site but not actually logged in. Their sessions might still be active because the manager would have recently logged out but is still surfing the site and the software needs to know that.

In this case I have made HttpRuntime.Cache logic to catch login events and data including user name and IP address, user name, etc. This method is called in:

public static void Application_AuthenticateRequest(object sender, EventArgs e)

I then use that data to perform logic calculating login status and other useful application details.

The following is quick and un-tested but if you are asking this sort of question I am sure you can clean it up :). I hope it helps.

 public static void HandleUserLoginLogic()
    {
        string UserName = HttpContext.Current.User.Identity.Name.ToString();
        if (UserName != null)
        {
            // if the user has logged in but we have not performed logic, do it now
            if (HttpRuntime.Cache["Authenticated_" + UserName] == null)
            {
                // absolutely confirm the user has logged in
                if (UsrMan.IsUserLoggedIn())
                {
                    // SETUP MY RUNTIME VARIABLES NOW
                    HttpRuntime.Cache["AuthenticatedIPAddress_" + System.Web.HttpContext.Current.Request.UserHostAddress] = UserName;
                    HttpRuntime.Cache["Authenticated_" + UserName] = true;
                    String[] roles = UsrMan.GetRolesForUser(UserName);
                    // handle roles for this user for future application uses in the future.
                    foreach (string role in roles)
                    {
                        // handle first time condition
                        if (HttpRuntime.Cache["AuthenticatedUserInRole_" + role] != null)
                        {
                            StringCollection scUserRole = (StringCollection)HttpRuntime.Cache["AuthenticatedUserInRole_" + role];
                            if (!scUserRole.Contains(UserName))
                            {
                                scUserRole.Add(UserName);
                                HttpRuntime.Cache["AuthenticatedUserInRole_" + role] = scUserRole;
                            }
                        }
                        // handle standard condition
                        else
                        {
                            StringCollection scUserRole = new StringCollection();
                            scUserRole.Add(UserName);
                            HttpRuntime.Cache["AuthenticatedUserInRole_" + role] = scUserRole;
                        }
                    }
                }
            }
        }

        // HANDLE LOGGED OUT CONDITION
        if ((HttpRuntime.Cache["AuthenticatedIPAddress_" + System.Web.HttpContext.Current.Request.UserHostAddress] != null) && (UserName == null))
        {
            string OldUserName = HttpRuntime.Cache["AuthenticatedIPAddress_" + System.Web.HttpContext.Current.Request.UserHostAddress].ToString();
            HttpRuntime.Cache["Authenticated_" + OldUserName] = null;
            String[] roles = UsrMan.GetRolesForUser(UserName);
            foreach (string role in roles)
            {
                StringCollection scUserRole = (StringCollection)HttpRuntime.Cache["AuthenticatedUserInRole_" + role];
                scUserRole.Remove(UserName);
                if (scUserRole.Count > 0)
                    HttpRuntime.Cache["AuthenticatedUserInRole_" + role] = scUserRole;
                else
                    HttpRuntime.Cache["AuthenticatedUserInRole_" + role] = null;
            }
        }
    }
    public static bool IsUserLoggedIn()
    {
        bool result = false;
        if (HttpContext.Current.User != null &&
      HttpContext.Current.User.Identity != null
          && HttpContext.Current.User.Identity.IsAuthenticated)
        { result = true; }
        return result;
    }
1

A fundamental problem with web-sites is that if the user closes their browser the web-site will never know. The implications point to a better solution. You have to re-think what it means to be "logged in" to a web-site.

There is generally never a reason why you should need to know when a user "logs out" of our web-site, because there is no "logging in". Every time a user visits a page, the web-server authenticates who they are; whether this be through:

  • NTML
  • Kerberos
  • HttpAuth
  • a session cookie
  • a persistent cookie
  • some other mechanism (e.g. you IPv6 address)

The user "logs in" every page load. After the browser has fetched the page, the user no longer exist (as far as the web-server is concerned). This means the user "logs out" as soon as their page has been fetched.

But logging in is slow

The usual reason to have a special logon event is so that you can cache the user's information (e.g. name, permissions). Rather than having to do an expensive database fetch each time they simply refresh a page, why not fetch it from the cache? But, as a good developer, we also need to expire that information when it's no longer needed. But since there is no way to know when the user is "done" using your web-site, there is no way to know when to flush the cached information.

So don't cache it.

SQL Server has a very advanced set of data caching mechanisms; just query for the data again.

Another tempation might be to store the data in the user's Session. That's perfectly valid; the information will remain there until the session ends. But a lot of people argue against storing data in a Session, as that eats up memory. They argue instead to store it in a database like SQL Server. Which brings up back to: just query it again in SQL Server.

If fetching the information is expensive, or time consuming, then you can cache it - but use a cached that was designed to be a cache:

Page.Cache

which is an alias of

Page.Application.Cache
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
0

Is it a big deal to implement the logic when you actually logout the user? Or, if you make it a more globally handled, create an event and subscribe to it, so that it will be event driven...

walther
  • 13,466
  • 5
  • 41
  • 67
  • It's only a big deal if i miss a spot, or don't understand every spot where a user can be logged out (when a session ends? when a session times out? when the application pool recycles? when their 50-year persistent cookie expires?) – Ian Boyd Jun 12 '12 at 20:36
  • @IanBoyd, are you sure you're trying to do the right thing here? What information are you trying to collect? – walther Jun 12 '12 at 21:15
  • Are you trying to log when a user logs out? I'm not sure if that is possible do do very reliably. If I'm right about keeping a log of user activity, could you instead keep a log of sessions? – user1429080 Jun 13 '12 at 09:33