23
votes

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.

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 mail.google.com/mail/u/0 & mail.google.com/mail/u/1, etc, with all urls relative to that 0/ or 1/)Wrikken
How is the javascript cookie sent to the server to authenticate?James Westman
Can someone explain what's with all the downvoting here? From the question to the answers authors took time to write out below?randombits
Could you elaborate your use case? To me it seems really odd that you would want that behavior.dave
Why the aversion to sessionStorage? For wider browser support? It appears only OperaMini doesn't support it now.Bob Stein

7 Answers

19
votes

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.

3
votes

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.
3
votes

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.

1
votes

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 === '[email protected]' and $pass === '111')
        {
            $id_user = 1;
            $name='John Doe';
        }
        if($email === '[email protected]' 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]
1
votes

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.

1
votes

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

-1
votes

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!