Stop people having my website loaded on multiple tabs
Asked Answered
S

10

44

I want users to browse my site from only one tab in their browser. How can this be done? Would I use javascript and cookies?

For example, I have a website: www.example.com - and I want my clients to only be able to visit the site from one single tab in one browser. If they open another tab and load the site (or a subpage of the site) - I want an alert "Can't open multiple instances", and then redirect them to an error page.

Once thing to note - if the user changes the address from www.example.com/action/door/mine.aspx to www.example.com - that should work fine, because the user is in the same (original) tab.

Any help will be appreciated. Thanks in advance.

Showpiece answered 13/6, 2012 at 4:22 Comment(2)
Well you could have a little javascript post a heartbeat request every now and then with the session id of the current user, before the page loads you check if this ajax heartbeat is coming in for this session, if it is, you terminate it.Dissolution
You might be able to do this for a limited set of cases, but in general it's impossible. Users can disable some or all scripts, refuse access to cookies and history, and some browsers allow starting an entirely new session in a tab, so other than IP sniffing (which the server can do easily) you're out of luck.Bold
B
13

EDIT2:

It's the exact thing which is mentioned at this answer, You need 2 IDs:

  1. One random one
  2. One consistent one (this will be our SSID actually, since you limit tabs of a single browser, it's better to get generated form browser's unique parameters)

You can generate consistent one from browser's user-agent or get it from server-side. store both of them server-side.
Store the random one in window.name property which is tab-specific.
Send a heartbeat every 1~2 seconds to your server containing both consistent ID and random one. if server fails to receive the heartbeat, it cleans up database and de-register dead clients.
on every browser's request, check window.name for the value. if it were missing, check with the server-side whether if the previous tab is closed or not (cleaned from database).

If yes, generate a new pair for client if no, reject them.


Two suggestions on top of my mind:
  1. Server-side (better): provide all your clients, a user name and password. request them on their first visit of your site to enter with their credentials. then on every other request, check for whether user with said credentials is already logged in or not.
  Client *
         |
         |
      Server ---> Check whether
                  Already logged
                     or not?
                  ______________
                   |          |
                  yes         no
                   |          |
                 permit     reject
                  them       them
  1. Client-side: If you really need a strong check of this, use evercookie to store an already-logged-in cookie on client's machine.

Side-note: Do know that every attempt in client side is not secure at all! client-side should help server-side, it shouldn't be used as the one and only source of security. even evercookies can be deleted so, give my first suggestion a go.


**EDIT:**

Evercookie is really doing a good job at storing most secure zombie cookies ever but since the library itself is a little bit heavy for browsers (storing a cookie takes more than 100ms each time) it's not really recommended for using in real-world web app.

use these instead if you went with server-side solution:

Behead answered 13/6, 2012 at 5:0 Comment(6)
thanks for the reply. some questions . 1:server side , i use session to store user's credentials and i have a login.aspx page to log in the user, because tabs in one browser shares session , so if i login the system in one tab(tabA), and copy the entire url(like www.xx.com/some/test.aspx), and open it from another tab(tabB), browser will consider user has logged in ,and in tabB, it will not redirect to login.aspx page. now i check if evercookie can solve this issue, hope it does , thanksShowpiece
@Showpiece No! if you went with the server side solution, don't use evercookie, use this instead: Way around ASP.NET session being shared across multiple tab windows. evercookie is for very very secure solutions, like banking and such (look at my edited answer).Behead
thanks , the solution by "way around..." , is it make every tab page as a single session? not solve my problem directly but in another way?Showpiece
@Showpiece Actually you should try to create a GUID, not a random one, a real GUID which gets regenerated algorithmic. what I mean is that you should try writing an algorithm which generated an ID from the browser. look at my second edit of the answer (I'm going to write it now)Behead
if server fails to receive the heartbeat How long is it defined as fail to receive?Burgee
@Derek your call, you may set up a Cron job form 1min ~ 1week but more optimized solution is to establish a socket client-side and clean up your database on socket's close/error events, server-side.Behead
V
35

UPDATE - 2020

Client side implementation:

We can make use of Broadcast Channel API which allows communication across browsing contexts (windows, tabs, frames or iframes) provided both contexts are from same origin.

A simple implementation to detect 2nd tab loading the website from the 1st tab:

    //in entry point of your app (index.js)    

    const channel = new BroadcastChannel('tab');

    channel.postMessage('another-tab');
    // note that listener is added after posting the message

    channel.addEventListener('message', (msg) => {
      if (msg.data === 'another-tab') {
        // message received from 2nd tab
        alert('Cannot open multiple instances');
      }
    });

This doesn't use localStorage or cookies and it even works if 1st tab is offline and 2nd tab is being loaded.

Note: This is not supported in Safari & IE11 yet :(

UPDATE - 2022

From March 2022, it is now officially supported on Safari 🥳

Take a note on its browser compatibility.

However, there's a polyfill available that does the job.

Venosity answered 6/6, 2020 at 12:18 Comment(4)
I had to use (msg.data === 'another-tab') to get it to work.Jaffna
@Jaffna by any chance are you calling channel.postMessage with an object { data : 'another-tab' }?Venosity
No, I was using the same as your example channel.postMessage('another-tab'). Here is what the object looks like in the console: MessageEvent {isTrusted: true, data: "another-tab", origin: "http://localhost:8080", lastEventId: "", source: null, …}Jaffna
I also had to check against msg.dataPurchase
A
32

I've created a simple solution for this. The master page layout creates a tab GUID and stores it in sessionStorage area of the tab. The using an event listener on the storage area I write the tab GUID to the sites localStorage area. The listener then compares the tabs GUID to the one written to site storage and if they differ then it knows more than one tab is open.

So if I have three tabs A,B,C then click something in tab C, tab A and B detect another tab is open and warn user of this. I haven't yet got to fixing it so the last tab used get's notification, work in progress.

Here's the JS I have in master page, plus in the login page I have a localStorage.Clear to clear last tab from previous session.

    // multi tab detection
function register_tab_GUID() {
    // detect local storage available
    if (typeof (Storage) !== "undefined") {
        // get (set if not) tab GUID and store in tab session
        if (sessionStorage["tabGUID"] == null) sessionStorage["tabGUID"] = tab_GUID();
        var guid = sessionStorage["tabGUID"];

        // add eventlistener to local storage
        window.addEventListener("storage", storage_Handler, false);

        // set tab GUID in local storage
        localStorage["tabGUID"] = guid;
    }
}

function storage_Handler(e) {
    // if tabGUID does not match then more than one tab and GUID
    if (e.key == 'tabGUID') {
        if (e.oldValue != e.newValue) tab_Warning();
    }
}

function tab_GUID() {
    function s4() {
        return Math.floor((1 + Math.random()) * 0x10000)
          .toString(16)
          .substring(1);
    }
    return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
      s4() + '-' + s4() + s4() + s4();
}

function tab_Warning() {
    alert("Another tab is open!");
}

Note: It's IE9+

Hope this helps.

Acoustic answered 24/9, 2015 at 11:40 Comment(3)
Its not working for me. Do i need to call any js function on load?Quadrumanous
@DanielASathishKumar all the JS should be in the page and you should run register_tab_GUID() on page load.Acoustic
if you duplicate a tab, the session storage will also will be in the new tab. linkSermon
L
16

Extending rehman_00001's answer to handle the case where you want the alert on the new tabs instead.

const channel = new BroadcastChannel('tab');
let isOriginal = true;

channel.postMessage('another-tab');
// note that listener is added after posting the message

channel.addEventListener('message', (msg) => {
    if (msg.data === 'another-tab' && isOriginal) {
        // message received from 2nd tab
        // reply to all new tabs that the website is already open
        channel.postMessage('already-open');
    }
    if (msg.data === 'already-open') {
        isOriginal = false;
        // message received from original tab
        // replace this with whatever logic you need
        alert('Cannot open multiple instances');
    }
});
Lactescent answered 10/2, 2022 at 17:24 Comment(0)
B
13

EDIT2:

It's the exact thing which is mentioned at this answer, You need 2 IDs:

  1. One random one
  2. One consistent one (this will be our SSID actually, since you limit tabs of a single browser, it's better to get generated form browser's unique parameters)

You can generate consistent one from browser's user-agent or get it from server-side. store both of them server-side.
Store the random one in window.name property which is tab-specific.
Send a heartbeat every 1~2 seconds to your server containing both consistent ID and random one. if server fails to receive the heartbeat, it cleans up database and de-register dead clients.
on every browser's request, check window.name for the value. if it were missing, check with the server-side whether if the previous tab is closed or not (cleaned from database).

If yes, generate a new pair for client if no, reject them.


Two suggestions on top of my mind:
  1. Server-side (better): provide all your clients, a user name and password. request them on their first visit of your site to enter with their credentials. then on every other request, check for whether user with said credentials is already logged in or not.
  Client *
         |
         |
      Server ---> Check whether
                  Already logged
                     or not?
                  ______________
                   |          |
                  yes         no
                   |          |
                 permit     reject
                  them       them
  1. Client-side: If you really need a strong check of this, use evercookie to store an already-logged-in cookie on client's machine.

Side-note: Do know that every attempt in client side is not secure at all! client-side should help server-side, it shouldn't be used as the one and only source of security. even evercookies can be deleted so, give my first suggestion a go.


**EDIT:**

Evercookie is really doing a good job at storing most secure zombie cookies ever but since the library itself is a little bit heavy for browsers (storing a cookie takes more than 100ms each time) it's not really recommended for using in real-world web app.

use these instead if you went with server-side solution:

Behead answered 13/6, 2012 at 5:0 Comment(6)
thanks for the reply. some questions . 1:server side , i use session to store user's credentials and i have a login.aspx page to log in the user, because tabs in one browser shares session , so if i login the system in one tab(tabA), and copy the entire url(like www.xx.com/some/test.aspx), and open it from another tab(tabB), browser will consider user has logged in ,and in tabB, it will not redirect to login.aspx page. now i check if evercookie can solve this issue, hope it does , thanksShowpiece
@Showpiece No! if you went with the server side solution, don't use evercookie, use this instead: Way around ASP.NET session being shared across multiple tab windows. evercookie is for very very secure solutions, like banking and such (look at my edited answer).Behead
thanks , the solution by "way around..." , is it make every tab page as a single session? not solve my problem directly but in another way?Showpiece
@Showpiece Actually you should try to create a GUID, not a random one, a real GUID which gets regenerated algorithmic. what I mean is that you should try writing an algorithm which generated an ID from the browser. look at my second edit of the answer (I'm going to write it now)Behead
if server fails to receive the heartbeat How long is it defined as fail to receive?Burgee
@Derek your call, you may set up a Cron job form 1min ~ 1week but more optimized solution is to establish a socket client-side and clean up your database on socket's close/error events, server-side.Behead
S
6

I know this post is pretty old, but in case it helps anybody, I recently looked into basically doing the same thing using localStorage and sessionStorage.

Similar Anthony's answer, it sets an interval to make sure the originating tab keeps the entry fresh, so that if the browser crashes or somehow closes without calling the unload event (included in the comments but not part of the code for testing purposes), then there would just be a short delay before the application would run properly in a new browser window.

Obviously, you would change the "tab is good", "tab is bad" conditions to do whatever logic you want.

Oh, and also, the createGUID method is just a utility to make the session identifier unique... it is from this answer to a previous question (wanted to make sure I wasn't taking credit for that).

https://jsfiddle.net/yex8k2ts/30/

let localStorageTimeout = 15 * 1000; // 15,000 milliseconds = 15 seconds.
let localStorageResetInterval = 10 * 1000; // 10,000 milliseconds = 10 seconds.
let localStorageTabKey = 'test-application-browser-tab';
let sessionStorageGuidKey = 'browser-tab-guid';

function createGUID() {
  let guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
    /*eslint-disable*/
    let r = Math.random() * 16 | 0,
      v = c == 'x' ? r : (r & 0x3 | 0x8);
    /*eslint-enable*/
    return v.toString(16);
  });

  return guid;
}

/**
 * Compare our tab identifier associated with this session (particular tab)
 * with that of one that is in localStorage (the active one for this browser).
 * This browser tab is good if any of the following are true:
 * 1.  There is no localStorage Guid yet (first browser tab).
 * 2.  The localStorage Guid matches the session Guid.  Same tab, refreshed.
 * 3.  The localStorage timeout period has ended.
 *
 * If our current session is the correct active one, an interval will continue
 * to re-insert the localStorage value with an updated timestamp.
 *
 * Another thing, that should be done (so you can open a tab within 15 seconds of closing it) would be to do the following (or hook onto an existing onunload method):
 *      window.onunload = () => { 
                localStorage.removeItem(localStorageTabKey);
      };
 */
function testTab() {
  let sessionGuid = sessionStorage.getItem(sessionStorageGuidKey) || createGUID();
  let tabObj = JSON.parse(localStorage.getItem(localStorageTabKey)) || null;

    sessionStorage.setItem(sessionStorageGuidKey, sessionGuid);

  // If no or stale tab object, our session is the winner.  If the guid matches, ours is still the winner
  if (tabObj === null || (tabObj.timestamp < new Date().getTime() - localStorageTimeout) || tabObj.guid === sessionGuid) {
    function setTabObj() {
      let newTabObj = {
        guid: sessionGuid,
        timestamp: new Date().getTime()
      };
      localStorage.setItem(localStorageTabKey, JSON.stringify(newTabObj));
    }
    setTabObj();
    setInterval(setTabObj, localStorageResetInterval);
    return true;
  } else {
    // An active tab is already open that does not match our session guid.
    return false;
  }
}

if (testTab()) {
  document.getElementById('result').innerHTML = 'tab is good';
} else {
  document.getElementById('result').innerHTML = 'tab is bad';
}
Secant answered 16/8, 2017 at 15:35 Comment(2)
When I open 2 tabs it then second give me "tab is bad" and first "tab is good". When I close first and I have have only one tab, second last tab show me still "tab is bad"Blocker
@step, it sounds like you are expecting this to re-evaluate. This example doesn't do that, but you could. It would look something like this, if you want it to check every 3 seconds, for instance: setTimeout(() => { if (testTab()) { document.getElementById('result').innerHTML = 'tab is good'; } else { document.getElementById('result').innerHTML = 'tab is bad'; } }, 3000); It might also be worthwhile to only do the set timeout if you are in the bad tab. Again, the code in this answer is really just an example. The logic can change based on your requirements.Secant
J
2
window.addEventListener('load', function () {
    if (localStorage.getItem('web_browser') == null) {
        // new tab
        localStorage.setItem('web_browser', 'true');
        window.addEventListener('unload', function() {
            localStorage.removeItem('web_browser');
        })
    } else {
        // duplicate tab
        return;
    }
})

Put this script at the beginning of html pages, where you don't want users to duplicate current page or tab.

Jeweller answered 23/1, 2022 at 11:22 Comment(1)
After reloading the page all the content is visible. It blocks first attempt only.Soule
H
1

The best way to solve this is to have one-time session IDs.

Eg, each page contain a session ID, that is valid for one visit, is unique, and random. When clicking any one link, it will use & invalidate the session ID, and the new page will have a new session ID.

This will force the user to always browse in the newest window or tab, and also prevents session stealing over the wire. Any attempt to reuse a old session ID should immediately kill also the active session IDs for that user.

Its also important to store, in the session management system, which pages is accessible from page X. So if page X (with session ID abc) contains links to page 1, 2 and 3, any attempt to visit page 4 with session ID abc, will fail and also kill the session.

This will force the user to always have one single session track, and always follow the logic on the site. Any attempt to go forward, back, using history or log entires, or opening multiple windows or tabs, will fail and logout the user in all windows, tabs and devices.

All this can be completely implemented on server-side, without any client-side logic.

Hagerty answered 25/11, 2015 at 22:42 Comment(0)
S
0

I wrote this to stop a call center page from being accessed in multiple tabs. It works well and is purely client-side. Just update the else if part to do what you want if it detects a new tab.

// helper function to set cookies
function setCookie(cname, cvalue, seconds) {
    var d = new Date();
    d.setTime(d.getTime() + (seconds * 1000));
    var expires = "expires="+ d.toUTCString();
    document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}

// helper function to get a cookie
function getCookie(cname) {
    var name = cname + "=";
    var decodedCookie = decodeURIComponent(document.cookie);
    var ca = decodedCookie.split(';');
    for(var i = 0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) == ' ') {
            c = c.substring(1);
        }
        if (c.indexOf(name) == 0) {
            return c.substring(name.length, c.length);
        }
    }
    return "";
}

// Do not allow multiple call center tabs
if (~window.location.hash.indexOf('#admin/callcenter')) {
    $(window).on('beforeunload onbeforeunload', function(){
        document.cookie = 'ic_window_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
    });

    function validateCallCenterTab() {
        var win_id_cookie_duration = 10; // in seconds

        if (!window.name) {
            window.name = Math.random().toString();
        }

        if (!getCookie('ic_window_id') || window.name === getCookie('ic_window_id')) {
            // This means they are using just one tab. Set/clobber the cookie to prolong the tab's validity.
            setCookie('ic_window_id', window.name, win_id_cookie_duration);
        } else if (getCookie('ic_window_id') !== window.name) {
            // this means another browser tab is open, alert them to close the tabs until there is only one remaining
            var message = 'You cannot have this website open in multiple tabs. ' +
                'Please close them until there is only one remaining. Thanks!';
            $('html').html(message);
            clearInterval(callCenterInterval);
            throw 'Multiple call center tabs error. Program terminating.';
        }
    }

    callCenterInterval = setInterval(validateCallCenterTab, 3000);
}
Sanguineous answered 29/4, 2017 at 3:58 Comment(1)
It block first attempt only. The page is visible after reolading.Soule
B
-1

Why do you want to do this?

Could try to do some ugly hacking, but the result would be: There is no way you could completely suppress this behaviour.

This could not be solved by JavaScript, because there is always the possibility that the user has disabled JavaScript in his browser, or allows only a certain subset.

The user could open a new browser, use a different computer, etc. to visit multiple pages at once.

But more important:

Also, your site would be the only site that has this behaviour and for this reason this will confuse everybody which uses your site, because it doesn't work like a web site should work. Everybody who tries to open a second tab will think: "This is odd. This website sucks because it different then websites should be. I will not come again!" ;-)

Boomkin answered 13/6, 2012 at 4:32 Comment(2)
Thanks Hippo, because our application has several functions like log system to record the actions from the client side, and with multiple tabs used , system will got confused and don't know what exactly happend, the track will be hard, if js disabled, well , we can do nothing about that,if so , he can't use our system. it is odd , but unfortunately we have to do that.Showpiece
Typically if some session is managed on the server and that it's easier to disable multiple tab than allowing the user to start two wizards simultaneously.Gadolinite

© 2022 - 2024 — McMap. All rights reserved.