13

Is it possible to make sessions per Browser tabs?

As example a user opened 2 tabs in his browser: Tab 1 and Tab 2

In Tab 1 he has a session:

$_SESSION['xxx'] = 'lorem';

And in Tab 2 the session is:

$_SESSION['xxx'] = 'ipsum';

Now on refresh i need to get the current session in the active tab. For example if the user refreshes Tab 2 i need to get the $_SESSION['xxx'] for tab 2 on load which is 'ipsum'. But $_SESSION['xxx'] shouldn't change on Tab 1.

Is there any option to save sessions per tab. If not what are other options to handle this issue?

Thanks for any help!

lhuber
  • 343
  • 1
  • 2
  • 12
  • By tab, you mean browser tab, right? – kav Jan 15 '16 at 10:03
  • This is very similar to this question: http://stackoverflow.com/questions/6067009/php-multiple-concurrent-sessions-per-user You may find your answer there. – 93196.93 Jan 15 '16 at 10:16
  • I face this problem and I'm using array like `$_SESSION['xxx']['uniquevalue']`, but my luck is I use it for handling submit form, so I can add a unique hidden field with unique value.... duno if my approach is acceptable or not but it's working.. :) – Nurkartiko Jun 23 '20 at 15:52

4 Answers4

13

PHP stores session IDs in cookies and cookies are per client (browser), not tab. So there is no simple and easy way to do this. There are ways of doing this by creating your own session handlers, but they are more hacks than solutions and as such come with their own risks and complexity. For whatever reason you may need this, I am quite sure that there is a better architectural solution than session splitting.

Auris
  • 1,262
  • 1
  • 9
  • 18
  • 1
    yep i made it with url parameters :) thanks for your answer! – lhuber Jan 15 '16 at 12:32
  • @Auris, a very good use for this is when you have multiple windows open. One has the News on, another has Facebook, and the third has your Magnificent Application which requires a login. With normal session cookies, you can login, then close that tab/window. But since the other tab/windows are still open, the session remains Alive. You can re-open a new tab/window and not need to login. I think it would be better if when the Application Window closes, the session dies, thus forcing a fresh login. With normal session cookies, the session doesn't die until all of the windows are closed. – UncaAlby Oct 28 '16 at 17:52
  • @UncaAlby Hi, Session actually dies when you close the window, it is simply rebuilt using the id stored in the cookie once you open it again if the cookie is not expired. However that is not the point of the question. Ihuber wanted to have 2 separate sessions for the same entity in different tabs. This is clinet functionality and there is no "clean" way to do this on server side. In your example you used 3 different apps. Those apps have their own session entities that have no access to each other (e.g. your super app can not manage variables in FB session container - web security 101). – Auris Oct 31 '16 at 15:43
7

I've been scouring the web for answers to this problem and have not yet found a satisfying solution. I finally pulled together something in JavaScript that sort of works.

//generate a random ID, doesn't really matter how    
if(!sessionStorage.tab) {
    var max = 99999999;
    var min = 10000000;
    sessionStorage.tab = Math.floor(Math.random() * (max - min + 1) + min);
}

//set tab_id cookie before leaving page
window.addEventListener('beforeunload', function() {
    document.cookie = 'tab_id=' + sessionStorage.tab;
});

HTML5 sessionStorage is not shared between tabs, so we can store a unique tab ID there. Listening for the beforeunload event on the window tells us that we're leaving (and loading some other page). By setting a cookie before we leave, we include our value in the new request without any extra URL manipulation. To distinguish between tabs you just have to check $_COOKIE['tab_id'] on the server and store sessions values appropriately.

Do note that Firefox behaves strangely, in that triggering window.open() will create a window that shares sessionStorage with its parent, giving you two tabs with the same ID. Manually opening a blank tab and then navigating to the target URL will give you separate storage. Chrome works for me in all of my tests so far.

I realize this is probably not the right answer, or even a "good" answer, but it is an answer.

allknowingfrog
  • 183
  • 2
  • 8
  • Yes, this may work, but again, this a hack. If you are building something personal and use this it's fine, but if you are building a propper commercial product and someone reviews your code, most likely you will simply get fired for this. You are storing app based sessions under tab id, that means on a public computer your sessions are avaialble to anyone opening that tab id. Also, what if user loads app components with different sessions in a different tab order? - like I said, this is good for playing around, but can't be used in a propper solution. – Auris Apr 04 '17 at 15:37
  • 7
    I think your criticism is a little unfair, as this is the start of a proper solution: instead of passing the session id from the cookie to the website, you would pass a tab id + session id from the cookie to the website. – frankster Jan 29 '18 at 12:29
  • @Auris CSRF tokens work to isolate sessions pretty much the same way, and they are a part of nearly every major framework. A CSRF token is secure because it is only used for blacklisting invalid requests, it alone does not whitelist a request. Similarly, this kind of tab token can only help to improve security as long as you handle it in a way that does not skip the line on all your other normal session handling. – Nosajimiki Mar 26 '21 at 13:41
  • Instead of storing sessionStorage.tab as a random number, I would suggest setting it as an auto-incrementing number. This will prevent any possible collisions from causing random bugs. – Nosajimiki Mar 26 '21 at 13:58
1

Here's my solution; we're using this to allow multiple app views open per client.

POST to get data: 'doStuff=getData&model=GTD&sn=6789&type=random&date=18-Dec-2018'

the controller then gets the data from the tables, build the object for the device, stores it to the session object and stores it in the variable as such. It generates a per tab guid so that the user can compare the same instrument with a different view in the UI.

$_session[$model][$sn][$type][$guid][$key];

the guid of course is also sent back in the data object so that the tab knows how to recall that data later on.

When the user wants to print the results to a file (pdf, etc) it sends a post with the relevant data in a POST.

'doStuff=saveFile&model=GTD&sn=6789&type=random&calDate=18-Dec-2018&guid=randomKey'

The controller then will pass that to the storage to retrieve.

Session class file example:

<?php
class Session {
public $fieldKey1;
public $fieldKey2;

public function GetStorage($model, $sn, $type, $guid, $key) {
    return $_SESSION[$model][$sn][$type][$guid][$key];
}

}

?>

the controller file:

<?php
require_once('session.php'); 
global $session; //from session class file
switch($_POST['doStuff']) {
  case 'saveFile':
     $session->GetStorage($_POST['model'], $_POST['sn'], $_POST['type'], $_POST['guid'], $key);
     break;
}
?>

This allows the user to have several views of the same data, without overwriting the data-set from each tab. If you don't need as much data granularity per tab, you can of course simplify the number of keys for your $_SESSION variable.

Morris Buel
  • 98
  • 1
  • 8
  • This answer inspiring me for another creation of php session like this `$_SESSION['xyz'] = "value1, value2"` – Nurkartiko Jun 22 '20 at 22:01
0

I've been taking a shot to make a web app with this feature.

It have not been matured and have constraints and flows, like having to transmit the session id in the url if your javascript make a post to an external php code depending on it, but it's functional and suits my needs (for now).

I am thinking of a more secure solution, so feel free to adapt it to your needs and give me your suggestions.

<?php
/**
* Split $_SESSION by browser Tab emulator.
* methods exemples are used whith :
* $session = new SessionSplit();
* as SessionSplit may reload the page, it has to be used on top of the code.
* 
*/
class SessionSplit{
    public $id;
    private $gprefix="session_slice_";
    private $prefix="";
    private $witness="";
    function SessionSplit($witness='witness'){
        if(session_status()===PHP_SESSION_NONE){
            session_start();
        }
        $this->witness=$witness;
        if($this->get_id()){
            $this->prefix=$this->gprefix.$this->id;
            //set a witness to 'register' the session id
            $this->set($this->witness,'true');
        }else{
            // force the session id in the url to not interfere with form validation
            $actual_link = "http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
            $new_link = $actual_link.(strpos($actual_link,'?')===false?'?':'&').
                'session_id='.$this->id;
            header('Location: '.$new_link); 
        }
    }
    private function get_id(){
        if(isset($_GET['session_id'])){
            $this->id=$_GET['session_id'];
            return true;
        }else{
            $this->new_id();
            return false;
        }
    }
    private function new_id(){
        $id=0;
        while(isset($_SESSION[$this->gprefix.$id.'.'.$this->witness])){$id++;}
        $this->id=$id;
    }
    // ----------- publics
    public function clearAll(){
        foreach($_SESSION as $key=>$value){
            if(strpos($key,$this->prefix.'.')===0){
                unset($_SESSION[$key]);
            }
        }
    }
    /**
    * $is_user=$session->has('user');
    * equivalent to
    * $is_user=isset($_SESSION['user']);
    * @param {string} $local_id 
    * @return {boolean}
    */
    public function has($local_id){
        return isset($_SESSION[$this->prefix.'.'.$local_id]);
    }
    /**
    * 
    * $session->clear('user');
    * equivalent to
    * unset($_SESSION['user']);
    * @param {string} $local_id 
    */
    public function clear($local_id){
        unset($_SESSION[$this->prefix.'.'.$local_id]);
    }
    /**
    * $user=$session->get('user');
    * equivalent to
    * $user=$_SESSION['user'];
    * @param {string} $local_id 
    * @return {mixed}
    */
    public function get($local_id){
        if (isset($_SESSION[$this->prefix.'.'.$local_id])) {
            return $_SESSION[$this->prefix.'.'.$local_id];
        }else return null;
    }
    /**
    * $session->set('user',$user);
    * equivalent to
    * $_SESSION['user']=$user;
    * @param {string} $local_id 
    * @param {mixed} $value
    */
    public function set($local_id,$value){
        $_SESSION[$this->prefix.'.'.$local_id]=$value;
    }
};
?>
yorg
  • 514
  • 5
  • 7