You will need to track session state server side for this, and expire sessions that have not had any activity within the last
X number of seconds. Say
60, which means that you will force logout on any sessions that haven't had any activity within the last minute.
If you do not want users to be logged out if they do not navigate to another page within each minute, you could have an AJAX keep-alive that will make a request to the page. I would suggest using
X/2 seconds for this check to allow time network latency. So every
30 seconds your AJAX will fire, sending the session cookie to the server that will update the
last action time recorded against the session to the server's current time.
You can still implement an inactivity timer that would expire the session if say you receive 15 minutes worth of AJAX keep-alives but no manual requests from the user.
At the end of the day, you need to realise that this is a client controlled situation so you can only take this so far. For example a user could auto refresh the page using a browser plugin to prevent automatic logout.
If this is a concern, you could increase the value of
X. If you application is highly sensitive, then maybe user education is the key. Teach your users that they must log out when they have finished using your application. You could detect whether users are timing out or explicitly logging out, and if it is more often the former you could display a prominent message on your site.
What are the highest-security websites currently doing in this space?
There is a highly secure way, however this would need to be implemented from the ground up within your web application architecture and cannot simply be dropped in. This is where Session IDs are placed in hidden form fields instead of being contained within a cookie. However, they must be submitted by POST (i.e. in the request body). Using this in combination with disabled caching will prevent a session being continued between browser restarts.
The downside of this approach is that every navigation action is required to submit a form.
For example we could have a form to handle navigation on each page constructed like so:
<form method="post" action="/navigate">
<input type="hidden" name="navigationPage" />
<input type="hidden" name="sessionId" value="12345678" />
navigationPage to the appropriate value (e.g.
orderHistory, etc) and then submit the form. The
/navigate URI will verify the
sessionId and then navigate to the page specified in
navigationPage if the session is valid. Using this method every page within your logged in session will have the same URI (
www.example.com/navigate in this case).
This is more secure than adding a query string value in that the Session ID will not be leaked in
referer headers in the case of any external links. External links can simply link directly to the resource and will not be the form POST that internal navigation uses.
Many banking systems employ a similar technique for extra protection against session hijacking within the session. In these scenarios
sessionId in the hidden form field will be regenerated on each page load which ensures that only a single web browser is navigating at one time. However, this will prevent the use of the back button because it forces a resubmission of POST data, or of opening links in separate browser tabs because the rolling ID will not be updated in both places. This is often used in combination with session cookies but it can be used in isolation. Actions that require additional information like money transfers can simply include extra form fields to be submitted with the main form.
Another advantage of this approach is that it is inherently secure against CSRF. If another page makes a cross site request it will be missing the
sessionId because it will not be automatically submitted the same way that cookies are.
This approach means you can also keep your current session timeout and there is no need for a heart-beat.