Share a Laravel (5.3) session across domains [img tags technique]

Can be used on multidomain applications

alberto1el
4 min readNov 7, 2016

The requirement is to share a Laravel session across multiple domains.

TL;DR: The application starts the session (generates the sessionid and file/record/…) for the visitor at the first domain and sets the sessionid in a cookie (default Laravel functionality), in that first request’s view we set img tags for each domain with a route were we attach the encrypted sessionid in the query parameter so a middleware can catch the sessionid and set the correct value on the browser cookie for each of those domains, after that, any future requests to any of the registered domains will retrieve the same session.

This solution should work with any Laravel session driver.

So it goes like this:

The default Laravel session is started from a middleware class called StartSession, that class is part of the default middleware stack for web routes which is named “web” and is defined in

App\Http\Kernel

We must work on the “getSession” method in StartSession::class in order to initialize the session that we want.

We will create our a class that extends StartSession:

In app/Http/Kernel.php do this:

//\Illuminate\Session\Middleware\StartSession::class, //comment
\App\Http\Middleware\StartSessionCustom::class, //add this

That StartSessionCustom will be your custom start session implementation, so go create that file:

app/Http/Middleware/StartSessionCustom.php

This file will consist on a class that extends Laravel’s StartSession and overwrite the handle and getSession methods:

<?php

namespace
App\Http\Middleware;

use Illuminate\Session\Middleware\StartSession;
use Illuminate\Http\Request;
use Closure;

class StartSessionCustom extends StartSession
{

protected $init_cookie = false;
public function handle($request, Closure $next)
{
$this->sessionHandled = true;
if ($this->sessionConfigured()) {
$session = $this->startSession($request);
$request->setSession($session);
/*CUSTOM CODE STARTS HERE */
\Session::forget('init_cookie');
if($this->init_cookie){
session( [ 'init_cookie' => 1 ] );
}
/* CUSTOM CODE ENDS HERE */
$this->collectGarbage($session);
}
$response = $next($request);
if ($this->sessionConfigured()) {
$this->storeCurrentUrl($request, $session);
$this->addCookieToResponse($response, $session);
}
return $response;
}
public function getSession(Request $request)
{
$session = $this->manager->driver();
/*CUSTOM CODE STARTS HERE */
if( $request->has('ck') )
{
$session->setId( \Crypt::decrypt($request->ck) );
}
else
{
$cookie_from_request = $request->cookies->get($session->getName());
if( empty($cookie_from_request) ){
$this->init_cookie = true;
}
$session->setId($cookie_from_request);
}

/*CUSTOM CODE ENDS HERE */
return $session;
}
}

Ill explain both method overrides:

The added code is in bold + italic.

The most important method is getSession.

The regular method checks if the request headers have the laravel_session cookie ( $request->cookies->get(‘laravel_session’)) if found then it tries to start the session with that id, if it doesn’t find one it generates a new session ID either way return a session instance.

Our custom implementation first looks for a ck parameter in the request, which if found must be the encrypted session id, that ck (query parameter) will be requested from the visitor’s browser when the visitor enters the first domain and loads a view, where there will be an img tag requesting the other domains with a specific route and the encrypted session id as a query parameter.

The else is for the first visit, we look for a sessionid in the request headers ($request->cookies->get($session->getName());) if none is set, then the setId($cookie_from_request) will generate a new session (file/record/etc) and its sessionid.

You can see a $this->init_cookie = true; that is set in the first framework load, and triggers an img tag on the first visit view, we will look into that now.

on the handle method we only added four lines which will attach an init_cookie variable to the session.

On your master_template file (or whatever is called the file were you put all your javascript libraries and head and body tags), go put this (anywhere within the body tags) :

@if(Session::has('init_cookie'))<img src="http://subdomain.secondomain.eu/set_cookie/?ck={!! \Crypt::encrypt(\Session::getId()) !!}" ><img src="http://thirddomain.mx/set_cookie/?ck={!! \Crypt::encrypt(\Session ::getId()) !!}" >

@endif

Notice that they are the same route, you should make that route in your routes file, it can actually be an empty route it just has to return a 200 ok response, once that route is hit the StartSessionCustom middleware will catch the ck parameter and set the same sessionid cookie for those domains on the visitors’ browser.

Route::get('set_cookie',function(){
return response(base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII='), 200)->header('Content-Type', 'image/png');
/* Returns 1px transparent image */
});

Now the last thing in the server side code we need to change, since the original StartSession class is a terminable middleware, we need our custom implementation also to be terminable, we achieve that by registering as a singleton, for that, create :

app/Providers/SessionServiceProviderCustom.php

then in config/app.php:

//Illuminate\Session\SessionServiceProvider::class, //comment
App\Providers\SessionServiceProviderCustom::class, //add this

We will extend the regular sessionserviceprovider and override the register method like so:

<?phpnamespace App\Providers;use Illuminate\Session\SessionServiceProvider;class SessionServiceProviderCustom extends SessionServiceProvider{    public function register()    {
$this->registerSessionManager();
$this->registerSessionDriver(); $this->app->singleton('App\Http\Middleware\StartSessionCustom'); }}

As you can see we are just registering our custom start session class as a singleton, in order for Laravel to save the session after the request cycle ends.

I found this solution to work for now, if you find any problem or have a better way of doing it, please send it below on the comments section, I wil update this article.

I just think of myself as a proactive internaut putting his grain of bytes.

--

--

alberto1el

San Diego — Tijuana — PHP Developer and Systems Administrator