A typical approach to this problem is to use an
inactivity time-out period.
This system enforces a maximum number of logins per account, while allowing for the situation mentioned: a user left the office without logging out, and attempts to login from his/her home workstation.
Here are the general lines of such a system
- Each account is associated with a number of concurrent logins (aka "seats") allowed (it seems the OP wished one and only one, for every account, but this could be more, and vary on an account basis).
- The license manager logic keeps a list of all accounts/users currently logged-in, along with a time stamp with their "last" activity.
- Before serving any page, the web application, calls the license manager (LM). The purpose is to allow the LM to update the timestamp of "last" activity, but also to deny the call in case the license was taken (more on this below)
- Upon each login, the license manager logic verifies that the number of seats taken doesn't exceed the amount specified for the account.
- If that is not the case, the LM simply adds the current session to the list of active session
- If that is the case, the LM check for sessions in the list which are older than the time-out period. If one is found, it disables it, and grants access to the new login. If none is found, the login is denied.
- upon each [explicit] log-out, the LM removes the corresponding session from the lists of active session.
Note that the general principle outlined above can have some variations, in particular:
- rather than silently and systematically invalidating the [typically oldest] timed-out session, one can inform the user currently attempting to logging about this situation and let him/her decide of the need to "kill" such a session.
- To avoid burdening the LM with each and every new page request, the web application can keep track on a per-session basis of the time since the session was last "refreshed" in the LM, and only call the LM if such time exceed say 1/3 of the time-out period.
Independently from the LM logic per-se, remember to keep a log of all the LM-related events (logins, logouts, inactive session "kills", refused logins...). Such logs should include the date/time, the IP address and other relevant info, and are useful when resolving issues associated with stolen passwords and such. Such logs also contain invaluable marketing, for example to find all accounts which appear to have too few seats (and could therefore purchase some ugrade), or to find at-risk accounts etc.
A few more considerations
- make it easy for users to log-out (log-out button/link on most every page, at a fixed location
- make it easy for users to report conflict / stolen password situation