ASP.NET provides a convenient and highly extensible authentication and authorization system that is shared by both ASP.NET MVC and ASP.NET Web Forms. I have cheerfully used it for years, without ever worrying about the fact that there is nothing in the system to prevent the same user from logging in multiple times. The other day, though, I received an email from a former student who was concerned about just that. Their business rules had changed, and now they needed the following behavior: if a user logs on twice with the same identity, the first user should be automatically logged out.
It was not something I had ever thought about before, and I became intrigued. I could imagine preventing the second user from logging in – but how to allow the second login to go through, and bounce the first user from the site? The idea intrigued me so much, I decided to put together a little test site.
I created a standard ASP.NET MVC3 Web application.
I then added code to the LogOn action method:
(The reason for the Session (“fixer”) is that if nothing is in the session, the ID can change between requests – and we’re relying on it staying the same).
Now that we have saved the sessionID of the current user (and overwritten any existing value), we can test for it on every request – and bounce out any user who doesn’t have the current one. In ASP.NET MVC, this is best done using an action filter. Here is the code for the LogUserOut action filter:
Now all we need to do is create an action method and View for Bounce, and assign the LogUserOut attribute to the appropriate action methods – in this case, Index and About:
Then I tested it. I logged on in Internet Explorer, created a new user and had no problem clicking Home and About.
Then I opened Firefox and logged in as the same user. Again, this was no problem and everything worked as it should.
Then I went back and clicked on a link in IE – and was bounced out: exactly what was supposed to happen.
So I logged in again in using IE.. and then went back to Firefox, where the current user was once again bounced out.
So the principle works. I wasn’t that happy with the solution, though. The use of the UserName is less than optimum – what if two users had the same name? So I reworked it to use ProviderUserKey as the cache key. That added a little complexity, as the MembershipUser is not available inside the LogOn action. To get around the problem, I redirected to a new Welcome action after a successful login, and used that to add the user to the cache. I had to do a bit of work to ensure the returnUrl was still honored and that unauthenticated users didn’t hit the Welcome method and cause issues, but it was a better solution as a result. (In Web Forms, I would have used the LoggedIn event to access the MembershipUser. I would also have inherited from a base page to provide the bounce logic, rather than using an action filter.) Another possible change would be to use a database instead of the cache, which would be slower but more reliable in a Web farm.
If you’re interested in the code for the complete solution, then you can download it from here. If you want to find out more about ASP.NET MVC, you might want to check out Building Web Applications with ASP.NET MVC. If you’re interested I learning more about security and state management in Web Forms, then I’d recommend Building Web Applications with ASP.NET and Ajax (of which I’m the author).