Sessions should not be shared across domains. It can be shared across sub-domains though and this can be achieved easily. For sharing it across domains, you will have to write tricky session handlers, which involves but not limited to passing it as a get parameter.
Share session from multiple domains but on same server
I am pointing multiple domains to the same Laravel application, i.e.:
http://mymaindomain.com
http://anothercustomdomain.com
http://aNamountofcustomdomains.com
However, every time I visit any of my custom domains, a new session is generated. When I refresh the page, I see a new session ID (very strange). However, if I refresh on my main domain, the session is remembered.
How can I configure the sessions, so that they are shared across multiple domains?
I doubt you can as session IDs are stored in cookie and cookies are identified by domain. This is a fundamental thing about the Web and not just Laravel.
I see, well could anyone explain why on all of my custom domains, every time I refresh (on the same custom domain) a new session is generated?
It "forgets" the session on page reload / page switch, which makes it impossible to make auth, checkout, etc..
Should not be the case, show your implementation.
@premsaurav what do you want to see? I make a Route group on the custom domain like this:
Route::group(['domain' => '{custom_domain}.{tld}'], function($domain) {
Route::get('/', ['uses' => 'PageController@show', 'as' => 'page.customdomain']);
});
The implementation of web middleware group.
This is my Kernel.php. Running Laravel 5.2
protected $middleware = [
'App\Http\Middleware\CheckForMaintenanceMode',
'Illuminate\Cookie\Middleware\EncryptCookies',
'Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse',
'Illuminate\Session\Middleware\StartSession',
'Illuminate\View\Middleware\ShareErrorsFromSession',
'App\Http\Middleware\RedirectFromCustomPageDomain',
'App\Http\Middleware\VerifyCsrfToken',
'App\Http\Middleware\GuestSessionUser',
'App\Http\Middleware\DetectCurrency',
'App\Http\Middleware\Locale',
'Xinax\LaravelGettext\Middleware\GettextMiddleware'
];
This is my config for session.php:
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Session Driver
|--------------------------------------------------------------------------
|
| This option controls the default session "driver" that will be used on
| requests. By default, we will use the lightweight native driver but
| you may specify any of the other wonderful drivers provided here.
|
| Supported: "file", "cookie", "database", "apc",
| "memcached", "redis", "array"
|
*/
'driver' => env('SESSION_DRIVER', 'file'),
/*
|--------------------------------------------------------------------------
| Session Lifetime
|--------------------------------------------------------------------------
|
| Here you may specify the number of minutes that you wish the session
| to be allowed to remain idle before it expires. If you want them
| to immediately expire on the browser closing, set that option.
|
*/
'lifetime' => 120,
'expire_on_close' => false,
/*
|--------------------------------------------------------------------------
| Session Encryption
|--------------------------------------------------------------------------
|
| This option allows you to easily specify that all of your session data
| should be encrypted before it is stored. All encryption will be run
| automatically by Laravel and you can use the Session like normal.
|
*/
'encrypt' => false,
/*
|--------------------------------------------------------------------------
| Session File Location
|--------------------------------------------------------------------------
|
| When using the native session driver, we need a location where session
| files may be stored. A default has been set for you but a different
| location may be specified. This is only needed for file sessions.
|
*/
'files' => storage_path().'/framework/sessions',
/*
|--------------------------------------------------------------------------
| Session Database Connection
|--------------------------------------------------------------------------
|
| When using the "database" or "redis" session drivers, you may specify a
| connection that should be used to manage these sessions. This should
| correspond to a connection in your database configuration options.
|
*/
'connection' => null,
/*
|--------------------------------------------------------------------------
| Session Database Table
|--------------------------------------------------------------------------
|
| When using the "database" session driver, you may specify the table we
| should use to manage the sessions. Of course, a sensible default is
| provided for you; however, you are free to change this as needed.
|
*/
'table' => 'sessions',
/*
|--------------------------------------------------------------------------
| Session Sweeping Lottery
|--------------------------------------------------------------------------
|
| Some session drivers must manually sweep their storage location to get
| rid of old sessions from storage. Here are the chances that it will
| happen on a given request. By default, the odds are 2 out of 100.
|
*/
'lottery' => [2, 100],
/*
|--------------------------------------------------------------------------
| Session Cookie Name
|--------------------------------------------------------------------------
|
| Here you may change the name of the cookie used to identify a session
| instance by ID. The name specified here will get used every time a
| new session cookie is created by the framework for every driver.
|
*/
'cookie' => 'laravel_session',
/*
|--------------------------------------------------------------------------
| Session Cookie Path
|--------------------------------------------------------------------------
|
| The session cookie path determines the path for which the cookie will
| be regarded as available. Typically, this will be the root path of
| your application but you are free to change this when necessary.
|
*/
'path' => '/',
/*
|--------------------------------------------------------------------------
| Session Cookie Domain
|--------------------------------------------------------------------------
|
| Here you may change the domain of the cookie used to identify a session
| in your application. This will determine which domains the cookie is
| available to in your application. A sensible default has been set.
|
*/
'domain' => str_replace(['http://', 'https://'], '', config('app.url')),
/*
|--------------------------------------------------------------------------
| HTTPS Only Cookies
|--------------------------------------------------------------------------
|
| By setting this option to true, session cookies will only be sent back
| to the server if the browser has a HTTPS connection. This will keep
| the cookie from being sent to you if it can not be done securely.
|
*/
'secure' => false,
];
'domain' => str_replace(['http://', 'https://'], '', config('app.url')),
Do you even see session cookie on any domain different that app.url?
As far as I know there's only one solution to this problem, I tried multiple things myself but wasn't able to get it working either. The method below is what worked, I was stubborn and thought that I could do it differently. A simple explanation is below (Single Sign On is what you're after)
What you need to do is use the database as your session handler, not files. All sites that need access to your session should have access to the database where sessions are stored. Let's say for example, you get this data in the database:
Session ID: ab9332ka IP: 192.168.0.1 Data: {"username":"Joshua"}
Everytime a request goes from one to another domain, you should append a querystring containing the session ID from the current session. Like so:
http://anotherdomain.com/?sessionID=ab9332ka
Within your code there must be some login that detects a session and then query the database for a result. If a result is found, you set the session ID manually and start the session:
session_id( 'mySessionId' );
session_start();
Be careful to check both the IP address and the session ID to prevent people from guessing someone elses SessionID and thus logging in as another person. See this topic: http://security.stackexchange.com/questions/14093/why-is-passing-the-session-id-as-url-parameter-insecure
Other useful information: http://stackoverflow.com/questions/14611545/preserving-session-variables-across-different-domains http://drupal.stackexchange.com/questions/840/enterprise-authentication-single-sign-on-and-user-provisioning-for-multiple-s https://github.com/jasny/sso
@Pendo thanks for you answer. using database was a solution I thought of as well. Maybe memcached would work aswell? Also; I'd say you need more protection that just IP. Maybe a random token in the request as well.
@premsaurav What do you mean exactly? The line you pasted was a hack I tried to implement earlier, but that did not solve it.
A random token in the request wouldn't help you either. As you need to put it in a seperate column in the database and validate it the same way as an IP adres. Maybe if the token was an encryption of some of the headers used by the original users that would work since that would at least "describe" the user more unique than just an IP address.
@canfiax I meant when you debug the page (on a custom domain that is different from app.url ) do you see the session cookie set in the browser. I believe , you wont as the cookie will be set for app.url.
Setting 'domain' => null, should persist the cookie for a custom domain (it will not be regenerated on every page load, i guess, unless something else is wrong).
However, you still cannot share it across domains. As I already mentioned earlier, and by @Pendo that you will have to manage the transfer at your end by appending a get parameter ( as an option) .
Thanks @premsaurav that solved it!
@Pendo you're answer was excellent, however can you provide with some more examples of how this could be implemented into Laravel?
We have the StartSession middleware in Laravel, and I guess this is where we need to add some functionality to check if we have a session_id in the query string, right?
My approach so far:
- Create a custom middleware for StartSession, that extends the original StartSession
- Override the method
getSessionand before it checks for session_id incookie, check if we have a token present in the Request - If we have a token, check if the token is valid and then retrieve the session_id. If we can't find anything, we fallback to the session_id from the cookies
See my code below. I just cannot get it to work properly. Whatever session_id I set in $session->setId('some_id') it gives me another session_id if I do session()->getId() in my code.
<?php
namespace App\Http\Middleware;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\Http\Request;
use App\SessionShare;
use Closure;
class StartSessionWithSharer extends StartSession
{
/**
* Get the session implementation from the manager.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Session\SessionInterface
*/
public function getSession(Request $request)
{
$session = $this->manager->driver();
/**
* Check if we can find a valid session token from saved records
*/
if($request->get('session_token') && !empty($request->get('session_token'))) {
$sessionShare = SessionShare::valid()->whereToken($request->get('session_token'))->first();
if($sessionShare)
$session_id = $sessionShare->session_id;
}
/**
* Fallback to session in browser
*/
if(!isset($session_id) || !$session_id)
$session_id = $request->cookies->get($session->getName());
$session->setId($session_id);
return $session;
}
}
I solved the issue. I was trying to call ->setId() on a session_id that did not exist.
When calling ->loginUsingId(), it will delete the old session. If you are using above solution, you also need to create your own custom SessionServiceProvider, like this:
<?php namespace App\Providers;
class CustomSessionServiceProvider extends \Illuminate\Session\SessionServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->registerSessionManager();
$this->registerSessionDriver();
$this->app->singleton('App\Http\Middleware\StartSessionWithSharer');
}
}
And then remove the old SessionServiceProvider from config/app.php and instead use above
Good job @canfiax, I haven't done this in Laravel yet so I wasn't able to provide much more of an answer than I did :)
@premsaurav - You mentioned it's simple, but what are the steps to share session across sub-domains? I've not been able to get it working. I have a 4.2 site that we're going to migrate to 5.2. I have a simple login page on 5.2. When a user logs in on the 5.2 app, I want to redirect them to the 4.2 app. Then upon logout, redirect to the 5.2 login page.
In addition to the above steps, you also need to adapt $middlewareGroups in app/Http/Kernel.php to use your custom StartSession middleware.
Please or to participate in this conversation.