With this enhancement, a business requirement came in to prevent multiple physical users from sharing a single user account. When I sat down to solve this problem, the first thing I did was fire up google and see what other people have done. All of the recommendations I found was focused on preventing a user from logging to the site and worked like this:
- User tries to login, site checks for some identifying piece of information from the client like a guid stored in a cookie.
- If there is no cookie then create one.
- The site then takes the cookie and looks up in a table (in memory or DB for example) and checks if this cookie is the same one for this user.
- If there is no entry in the table or the cookie is the same the user is able to login.
- If the cookie doesn't match the user is locked out.
This is an ok algorithm, but its main weakness is that it assumes that we know when a user logs off, which is impossible in an ASP.Net page. One idea and I forgot where I saw this was to use ASP.Net cache to store this entry, we can then set it to auto expire so that if a user gets locked out it would only be for a couple of minutes.
When I thought about this model, the biggest thing I didn't like is that it punishes the user, if for example his browser crashes. It would be even worse if it crashes because of my site and then I prevent him from logging in for five minutes.
I wanted to come up with a solution which works like many of the IM's out there. I wanted a last one in wins model. So how did I achieve this within Asp.Net?
- User logs in, and a new Guid is generated, and stored in a non-persistent cookie, and its stored in HttpContext.Current.Cache, using the user's login as a key.
- A client side timer is initialized in the main page of the application. This works nicely for us since the user never navigates away from this page currently; however, this same approach could be adapted to other navigation structures.
- This timer kicks of a call to a very simple Ajax enabled WCF service.
- The service checks the guid stored in the user's cookie.
- The services looks into cache for the guid, if no guid is found the value in the cookie is stored. This handles the case of the cache being flushed out.
- If the cookie doesn't match the cached item, then a false value is returned to the client.
- If the cookie matches the value in the cache a true value is returned.
- The client then navigates to an error page if the value is true, it also tracks any dialogs that were opened and navigates them as well.
This algorithm allows us to enforce the requirement to limit concurrent users, while still preventing frustration of a user being locked out because their browser crashed.
ps. If I ever get around to building any diagrams I will update this.