30

I am interested in having users be able to login and logout with multiple user session cookies on my web app. Currently, authentication is done standard and a unique identifier allows me to authenticate a user when they visit our site back if they present an auth token that's available in their cookie. Typical use cases apply in that if the user logs out from one tab, it logs them out of another tab. Right now it requires having the user login from two unique browser instances in order to be able to login to two different accounts.

Is there a non-HTML5 way (using standard javascript cookies) to have tab-specific cookie identifiers? I'm assuming that there is no clear cut way of going about this and it would require some kind of hack + cooperation from the backend. If there is a solution that makes sense without using HTML5, that would be ideal.

randombits
  • 47,058
  • 76
  • 251
  • 433
  • 7
    You could go the google way: use the same session, but allow 2 users to be present in that session, and store the current 'user' in the url, and make all urls relative to that user. That plays havoc with SEO though, so I'd only use it for pages which a crawler shouldn't see anyway. (For instance, my multiple Google Apps inboxes go to https://mail.google.com/mail/u/0/ & https://mail.google.com/mail/u/1/, etc, with all urls relative to that 0/ or 1/) – Wrikken Nov 25 '14 at 22:06
  • How is the javascript cookie sent to the server to authenticate? – James Westman Nov 29 '14 at 16:12
  • 2
    Can someone explain what's with all the downvoting here? From the question to the answers authors took time to write out below? – randombits Dec 01 '14 at 15:40
  • Could you elaborate your use case? To me it seems really odd that you would want that behavior. – dave Dec 01 '14 at 23:01
  • 1
    Why the aversion to sessionStorage? For wider browser support? It appears [only OperaMini](https://caniuse.com/#feat=namevalue-storage) doesn't support it now. – Bob Stein Aug 21 '19 at 13:29
  • Nowadays sessionStorage may be undesirable because if you duplicate a tab you duplicate the session storage of that tab. If anyone wants to try it out, I believe some of these .htaccess solutions would be better done by doing the rewrite client-side with a service worker, and then you wouldn't have to worry about people sharing URLs that contain session IDs in them. – hostingutilities.com Dec 07 '22 at 23:05
  • Alternatively you could use sessionStorage anyways, and use the postMessage API to send out a "marco" event with a tab ID attached to it, and if another tab responds "polo" with the same tab ID, then you know a tab has been duplicated, and you could show a warning to the user. If no tabs have been duplicated, then the data in sessionStorage is guaranteed to be unique per tab. – hostingutilities.com Dec 07 '22 at 23:10

7 Answers7

22

You can't.

There are ways to deal with this condition, but none of them are simple.

If you want, you have to tell user to do like this: How to geek

From docs: Data stored using sessionStorage do not persist across browser tabs, even if two tabs both contain webpages from the same domain origin. In other words, data inside sessionStorage is confined to not just the domain and directory of the invoking page, but the browser tab in which the page is contained in. Contrast that to session cookies, which do persist data from tab to tab.

Prateek
  • 6,785
  • 2
  • 24
  • 37
  • 2
    Note that if you open a new window on the same origin using browsers' "Duplicate Tab" feature or `window.open`, for instance, both windows (which might be tabs) will share the same `sessionStorage` (tested on Chrome and Firefox). – Samuel Mar 26 '21 at 19:43
  • 4
    @Samuel, that is **almost correct**. When calling ``window.open`` the ``sessionStorage`` is **copied** to the new tab/window. So they have the same content only until you change either of them. (tested on Chrome and Firefox) – Jack Miller Jan 20 '22 at 05:48
3

I achieved similar behavior some time back. So, what I do is something like this:

  1. For this to work, you need to carry the sessionId in the url or as part of the page content.
  2. When login page is loaded, delete the sessionId cookie.
  3. When login for is submitted, server gives you login page along with sessionId in the url or as part of html response body.
  4. From now onwards, before every server call, set the session cookie to the one that you have in the url or page content.
  5. So, each tab will set its own cookie before any server call which would make the request land with the right session on the server.
thunder
  • 230
  • 1
  • 8
3

Before anything, this solution works if you use relative URLs only! (for images, links and even Ajax calls)

Use sessions as you would in any ordinary scenario with one small change. Instead of identifying users with each session ID, you will identify a machine (a browser) by each session ID. So when requests arrive at server, it identifies a bunch of users who are using your website on that computer. Each user will have his own sub-identifier (it could be a sequential counter or a random number). Putting it simple, your session data (identified by session ID in the cookies) holds an associative array. Each entry of this array holds session data for one particular user identified by sub-identifier. For instance, in PHP, if your user's sub-identifier is user0, then you can access this user's session data like:

<?php
session_start();
$user_data = $_SESSION['user0'];

Next is how to pass on user's sub-identifier.

You can use webserver's URL rewrite. You need to come up with a pattern which can be considered as an ordinary folder name, while there's no folder named like that. For instance:

RewriteEngine On
RewriteRule ^user(\d+)\/(.*)$ $2?sub_id=$1 [QSA,L]

In this example, you are not allowed to have any folders like user0, user1 etc. If some request asks for http://domain.com/user0/index.php it will be rewritten to http://domain.com/index.php?sub_id=user0. Now in index.php you'll have:

<?php
session_start();
$user_data = $_SESSION[$_REQUEST['sub_id']];

And you should use $user_data instead of $_SESSION from this point forth. The only thing that remains is how to generate sub-identifier for the first time. That's relatively easy, you can:

<?php
session_start();
if (!isset($_REQUEST['sub_id'])) {
    $sub_id = 0;
    while (isset($_SESSION["user{$sub_id}"])) {
        $sub_id++;
    }
    $_SESSION["user{$sub_id}"] = array();
    header("Location: /user{$sub_id}".$_SERVER['REQUEST_URI']);
    die();
}
else {
    $user_data = $_SESSION[$_REQUEST['sub_id']];
}

At the end, everything will work only if all your URLs are relative! Each absolute URL which does not start with /user0/ will be considered a new user and will lead to a new entry in the session.

The benefit of this approach is that your current code will work with minimum effort, as long as URLs are already addressed relatively.

Mehran
  • 15,593
  • 27
  • 122
  • 221
1

This is a simple example of how you can create a system in which a user can log in to multiple accounts. This is no safety checks and must be added. This code can be much better to write and optimize.

inc.php

https://github.com/maksa9/multiple-user-login/blob/master/inc.php

This file is included into each php script.

This part check which user is logged and which account is active. Here are functions that create the proper path to the php scripts according to the active account

// check which user is logged and which account is active
if(isset($_GET['user'])) $id_user = (int)$_GET['user'];
if($id_user > 0)
{
    if(isset($_SESSION['user'][$id_user]))
    {        
        $user_name = $_SESSION['user'][$id_user]['name'];
        $user_email = $_SESSION['user'][$id_user]['email'];                
    }
    else
        gotToLoginForm();
}

// If the user id is not specified and there is a user session, finds another id
if($id_user == 0 and isset($_SESSION['user']))
{    
    $sess = $_SESSION['user'];

    $id_user = (int)key($sess);

    if(isset($_SESSION['user'][$id_user]))
    {        
        $user_name = $_SESSION['user'][$id_user]['name'];
        $user_email = $_SESSION['user'][$id_user]['email'];  

        define('ID_USER',$id_user);

        gotToIndex();              
    }
    else
        gotToLoginForm();

}

define('ID_USER',$id_user);

loginform.php

https://github.com/maksa9/multiple-user-login/blob/master/loginform.php

Simple form to login with post method.

login.php

https://github.com/maksa9/multiple-user-login/blob/master/login.php

Login user. simulates a query to the database.

if(isset($_POST['email']))
    if(isset($_POST['pass']))
    {
        $email = $_POST['email'];
        $pass = $_POST['pass'];

        $id_user = 0;

        // simulates a query to the database
        if($email === 'test1@test.com' and $pass === '111')
        {
            $id_user = 1;
            $name='John Doe';
        }
        if($email === 'test2@test.com' and $pass === '222')
        {
            $id_user = 2;
            $name = 'Doe John';
        }

        // login user
        if($id_user > 0)
        {
            // checks if the user is already logged
            if( !isset($_SESSION['user'][$id_user]))
            {
                $_SESSION['user'][$id_user] = array('email'=>$email, 'name'=>$name);
            }

            //go to main page 
            $page = ROOT.'user/'.$id_user.'/index.php';            
            header('Location: '.$page);
            exit;

        }        
    }

index.php

https://github.com/maksa9/multiple-user-login/blob/master/index.php

Main page of the application.

<div>
    <h1>Welcome: <?php echo $user_name ?> (<?php echo $user_email ?>) [<?php echo $id_user ?>]</h1>


    <p><a href="<?php echo returnUrl('swap.php',$id_user)  ?>">Choose an account</a></p>    
    <p><a href="<?php echo returnUrl('loginform.php',$id_user)  ?>">Login with the another account</a></p>        
    <p><a href="<?php echo returnUrl('logout.php',$id_user)  ?>">Log out</a></p>

</div>

swap.php

https://github.com/maksa9/multiple-user-login/blob/master/swap.php

Allows the user to choose the account.

foreach($_SESSION['user'] as $idus => $userA)
{
    echo '<p><a href="'.returnUrl('index.php',$idus).'">'.$userA['name'].' ('.$userA['email'].') ['.$idus.']</a></p>';
}

logout.php

https://github.com/maksa9/multiple-user-login/blob/master/logout.php

Logout user. Check for active user accounts and redirects them if any.

unset($_SESSION['user'][ID_USER]);

if(count($_SESSION['user']) == 0) 
    unset($_SESSION['user']);


// checks for active user accounts and redirects them if any
if(isset($_SESSION['user']))
{        
    $sess = $_SESSION['user'];

    $id_user = (int)key($sess);

    if(isset($_SESSION['user'][$id_user]))
    {            
        $page = ROOT.'user/'.$id_user.'/index.php';            
        header('Location: '.$page);
        exit;               
    }        
}    

.htaccess

https://github.com/maksa9/multiple-user-login/blob/master/.htaccess

Options +FollowSymlinks
RewriteEngine On

RewriteRule ^user\/([0-9]*)\/index.php$ index.php?user=$1 [NC,L]
RewriteRule ^user\/([0-9]*)\/logout.php$ logout.php?user=$1 [NC,L]
RewriteRule ^user\/([0-9]*)\/login.php$ login.php?user=$1 [NC,L]
RewriteRule ^user\/([0-9]*)\/loginform.php$ loginform.php?user=$1 [NC,L]
RewriteRule ^user\/([0-9]*)\/swap.php$ swap.php?user=$1 [NC,L]

RewriteRule ^user\/$ index.php [NC,L]
RewriteRule ^user$ index.php [NC,L]
dm4web
  • 4,642
  • 1
  • 14
  • 20
1

You cant

When a cookie is created it is possible to control its visibility by setting its 'root domain'. It will then be accessible to any URL belonging to that root. For example the root could be set to "example.com" and the cookie would then be available to sites in "www.example.com" or "xyz.example.com" or "example.com". This might be used to allow related pages to 'communicate' with each other. It is not possible to set the root domain to 'top level' domains such as '.com' or '.co.uk' since this would allow widespread access to the cookie.

By default cookies are visible to all paths in their domains, but at the time of creation they can be retricted to a given subpath - for example "www.example.com/images".

so any tab which is having same root domain can access that cookie.

Laxmikant Dange
  • 7,606
  • 6
  • 40
  • 65
1

The session cookies are server specific AFAIK, so what you could do is set up different DNS names for the same server, e.g. subdomains like: session1.myserver.com, session2.myserver.com, session3.myserver.com

Chris
  • 834
  • 1
  • 10
  • 23
  • I have just checked this on my server and this actually does the job. You need to do some more tweeking if you have Rewrite-Rules and redirections to https - in that case it's going to be quite nasty. – Chris Dec 04 '14 at 21:01
-1

Well @dm4web's answer is kind of correct but you have to pay heed to his security warnings though. The best thing that you can do is take a bi-directional approach.

Direction One

Regular Login.
Create a Unique session ID and pass it via the URL.

Direction Two

Check Session via i) Logged In User and ii) Check Session ID via URL Param

Now, let's take an example:

$usrname: Fool
$psswd: dm4web

PHP Code

session_start();
//all inputs should be sanitized
$sql = "SELECT * FROM `users` WHERE `usrname`='".$usrname."' AND `psswd` = '".$psswd."'":
$dbh = new PDO('odbc:db', 'db2inst1', 'ibmdb2');
$count = $dbh->exec($sql);
if($count > 0){
 //Guy is logged in
 $a = session_id();
 //**Use this $a in every URL parameter under current session**
}
else {
 //Go f**k yourself >> to the user ;)
}

But you should notice that you can't directly jump into that user/pass match scheme. First you have to ensure that you find out if the user is already logged in or not. Also, based on the SESSION Cookie from PHP, you figure out that

  1. If there is an active log in on the machine
  2. If there is an active login on the URL [vide the $a from the session_id thing]

You match the URL parameter under all circumstances, cross reference with the SESSION cookie and proceed!

Good Luck! Let me know if you've any more questions!

Chris Roy
  • 945
  • 1
  • 7
  • 19