Laravel: share session data over multiple domains - session

I'm building a multi-domain/multi-store ecommerce application in Laravel and would like to keep the user logged in when he or she changes from store to store.
But for as far as I know Laravel's Auth service saves the logged in user in the session and the sessions can't be accessed by other domains.
Is there a way (maybe a package) to achieve this, without leaving my application prone to possible security problems?
Thanks in advance!

Capture the session id Session::getId() in Domain A
send the captured session id via HTTP POST to Domain B
Access the sent session id in domain B $sessionid_from_domainA = $_POST['session_from_A']
Set session in domain B Session::setId($sessionid_from_domainA)
Start Session in domain B Session::start()

If you want to share the session between multiple subdomains in that case you have to set the domain name config/session.php has set the domain name.
Example: if you have new.example.com and test.example.com so you have to set the domain name as example.com
'domain' => env('SESSION_DOMAIN_URL','.example.com')
Solutions there worked for me, specifically setting the domain and then clearing my browser cookies & cache.

On domain A create an image like so <img src="https://DOMAINB.com/setcookie?id={{ Session::getId() }}" style="display:none;" />
On Domain B create a Route like so:
.
Route::get('setcookie', function(){
Session::setId($_GET['id']);
Session::start();
return 'Cookie created';
});`
Done, Now you should be able to get your user by $user = Auth::User;

I know this is not exactly what was asked for, but, for development and testing purposes, I did this:
In config/session.php, try changing this line
'path' => '/',
Into this
'path' => '/;SameSite=None; secure',
allowed me to authenticate from different domains.
Now, you should be able to write a simple middleware to prevent unwanted hosts. Something like this.
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Closure;
class TrustedHosts{
public function handle($request, Closure $next){
//$host = $request->getHost();
$host = $request->headers->get('origin');
$enviroment = env('APP_ENV');
if ( $enviroment == 'development' ) {
$trustedHosts = array('localhost', 'dev.mydomain.com');
}
else {
$trustedHosts = array('anotherdomain.com', 'mydomain.com');
}
$isHostTrusted = in_array($host, $trustedHosts);
if ( !$isHostTrusted ) return response("I'm a teapot", 418); //Or any other code and message that you prefer.
return $next($request);
}
}
And group it in the middleware group that includes the session stuff.

I am working on something like that too a single sign-on system, still working to find a solution, but here is a start http://laravel.io/forum/03-14-2014-multiple-domains-how-to-share-login
On laravel you can change the /app/config/session.php driver to cookie
Edit:
This is what I have done.
You can share cookie accross domains using pixel images.
For example when you login on domain1.com you want to create a cookie on domain2.com, right after login on domain1.com you need to have something like this
<img src="http://www.domain2.com/create-cookie?param=hash">
on domain2.com the route above:
will check first if a user is logged in
if its not logged in will read the hash (for example email address), check if there is a user with that email, and login it there, also set a cookie

You can manually set which domain the session cookies will be registered, and thus have them persist across different sites. Just edit config/session.php, in the following section:
<?php
/*
|--------------------------------------------------------------------------
| 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' => null,
?>
You're still restricted to a single top-level domain, though. So you can have store1.shops.com and store2.shops.com sharing sessions, but not myshop.com and shopsmart.com.
If you need something in this format, you'll probably fare better by creating an authentication service, and using access tokens to validate the credentials. You might also give a look at services like OneLogin.

Based on this answer of Chirag Prajapati. Yo could use $_SERVER['HTTP_HOST'] in the env entry:
From /config/session.php
'domain' => env('SESSION_DOMAIN', $_SERVER['HTTP_HOST']),
Then from the app root: php artisan config:clear and don't keep attention to warning messages, just be sure at the end you have gotten Configuration cache cleared! By this way your app session will work fine on whatever domain you have.

Related

Laravel 5.1 use session to restrict direct access using urls users based on user role

I have 2 laravel projects, 1 for the front end where i m using html css angularjs. The second for api controllers. I call using http post and get the api controllers functions using angularjs to get content data.
In the front end i have a menu this menu appears differently based on user role, if admin or no.
This is done. My problem is the access for views using the url in the browser.
So I have a query where I get for each user what modules in the menu can he see. Now I'm putting the result in Laravel session.
$menu = DB::select menu by user id ... //Getting menu query based on user if admin or no
session(["menu" => $menu);
return session('menu');
I'm getting the results and the menu is showing good in the website based on the logged user if he s admin or no.
Now, to solve the direct url access issue, I want to use this session and compare the url to this session, if the url exists in the session i will let him access, if no i will redirect him to somewhere.
any idea?
I would strongly suggest looking at the Laravel documentation on Authorization before going too far down a custom implementation:
https://laravel.com/docs/5.1/authorization
Without knowing more about how your front-end and back-end applications interact with each other, it is a little difficult to get into speciifics but i shall do my best.
Each page returned by Laravel has access to a Request object which contains information about the request which returned the page. You can access this Request and its assocaited Route using Laravels helper functions (if you are not passing it to the view already). The getPrefix() method will return the root relative url which you can then use as you see fit. For example:
// Return and store the URL as a string
$url = request()->route()->getPrefix();
// Check your session for the URL/s you want to allow and compare to the stored URL
if (session()->get('URL') == $url) {
// User is allowed access to the page
// Do something ...
} else {
// User is not allowed access to this page
// Redirect back or to a route of your choice
return redirect()->back();
}
I hope this gives you some ideas, good luck!

Laravel shared cookie detection issue in domain and subdomain

I am working on Laravel 5.4.30.
Imagine that we have a domain example.com and a subdomain of dev.example.com. The main domain is for master branch and the dev subdomain is for develop branch. We have cookie notice system that will be hidden after clicking on Hide Cookie Notice button. This works by setting a cookie forever.
We have set the SESSION_DOMAIN configs to each domain for each environment.
For main domain:
SESSION_DOMAIN=example.com
For dev subdomain:
SESSION_DOMAIN=dev.example.com
Now the issue comes from here. If we go to the example.com and click on hiding the cookie notice, a cookie will be set forever for main domain. After that we go to the dev.example.com and do the same. So a cookie will be set for subdomain as well. But this cookie has been set after previous one. (The order is important)
Now if we refresh the subdomain, we will see that notice again! (not hidden) The browser has read the main cookie because of .example.com set in domain parameter of cookie in the browser, so every subdomain will be affected. But the view still shows the notice because it cannot read any cookie for hiding.
Anyway I don't want to share that cookie across all subdomains. How can I achieve that? I think I should add a prefix for cookie name. But I don't know how to do it, that laravel automatically adds prefix to cookie name.
Any solutions?
You need to implement your own "retrieving" and "setting" a cookie.
Retrieving (has, get) cookies
Create yourself new class (anywhere you like, but I would do app/Foundation/Facades/) with name Cookie.
use \Illuminate\Support\Facades\Cookie as CookieStock;
class Cookie extends CookieStock {
//implement your own has(...);
public static function has($key)
{
return ! is_null(static::$app['request']->cookie(PREFIX . $key, null)); //get the prefix from .env file for your case APP_ENV
}
//implement your own get(...);
public static function get($key = null, $default = null) {...}
}
Now open up config/app.php and change corresponding alias (cookie).
Setting (make) cookies
Create yourself new provider (use artisan), and copy-paste code from Illuminate\Cookie\CookieServiceProvider.php and change namespaces.
Again open up config/app.php and change corresponding service provider with the new one.
Create yourself new class (anywhere you like, but I would do app/Foundation/Cookie/) with name CookieJar.
use \Illuminate\Cookie\CookieJar as CookieJarStock;
class CookieJar extends CookieJarStock {
//Override any method you think is relevant (my guess is make(), I am not sure at the moment about queue related methods)
public function make($name, $value, $minutes = 0, $path = null, $domain = null, $secure = false, $httpOnly = true)
{
// check before applying the PREFIX
if (!empty($name)) {
$name = PREFIX . $name; // get the PREFIX same way as before
}
return parent::make($name, $value, $minutes, $path, $domain, $secure, $httpOnly);
}
}
Update the code in your own cookie service provider to use your implementation of CookieJar (line 19).
Run $ composer dump-autoload, and you should be done.
Update
Since BorisD.Teoharov brought up, that if framework changes signature of CookieJarStocks make() (or any other cookie related function) in between the major versions, I made a example repository, that includes a test that can be used as is and it will fail if signature change happens.
It is as simple as this:
public function test_custom_cookie_jar_can_be_resolved()
{
resolve(\App\Foundation\Cookie\CookieJar::class);
$this->assertTrue(true);
}
Detailed how to can be inspected in the corresponding commit diff.
I've setup test environments to make sure, I'm not missing any details.
As in my former answer, I thought invalidating cookies will be sufficient for that case, but as #BorisD suggested it is not, and I've confirmed that on my tests.
So there are a few important notes, coming from my experiences...
Don't mix Laravel versions in subdomains - If using SESSION_DOMAIN you need to make sure your Laravel version matches (between root and subdomains), cause I've experimented with 5.4 under example.com domain and 5.6 under dev.example.com. This showed me some inconsistency in dealing with Cookies, so some important changes have been done, between these versions, and you can be sure it will not work correctly if you mix versions. I finally ended up with Laravel 5.6 on both domains, so I'm not 100% sure if that works on Laravel 5.4, but I think it should.
Make sure all your subdomains use the same APP_KEY - otherwise, Laravel will be unable to decrypt the Cookie, returning null value, cause all encryption/decryption in Laravel uses this app key...
SESSION_DOMAIN. In SESSION_DOMAIN I've pointed the same root domain like example.com for both domains. With this setting, I can create a cookie on root domain, and retrieve it correctly on both domains. After that setting, creating a cookie on subdomain forces root domain to receive new value from subdomains cookie also, and they are overridden. So I guess everything works here as requested in the original question.
Cookie make parameters - In case you want to use a subdomain in SESSION_DOMAIN, you can safely do that also. However, you need to make sure, important let's call them global cookies are defined in a bit different way. Cookie make syntax:
Cookie make(string $name, string $value, int $minutes, string $path = null, string $domain = null, bool $secure = false, bool $httpOnly = true)
So what's important here, you need to put your root domain for this particular cookie on creation like this for example:
return response($content)->cookie('name','value',10,null,'example.com')
Conclusions:
With this config, you should be able to access your Cookies properly under subdomains and your root domain.
You may probably need to update your Laravel installations to 5.6, which will force you to upgrade to PHP 7.1 at least (there were some changes to cookies in php also)
And finally, in your code, don't rely on Cookie existence, but on its values only (I don't know if that's in your case).
You could set a prefix for the cookie name depending on the environment.
First, add COOKIE_PREFIX to your env file.
COOKIE_PREFIX=dev
Then, use it when setting your cookie
$cookie = cookie(env('COOKIE_PREFIX', 'prod') . '_name', 'value', $minutes);
Then, retrieve it like so
$value = $request->cookie(env('COOKIE_PREFIX', 'prod') . '_name');
One of reason for that is for both app
APP_KEY and APP_NAME
should same in .env file,
that worked for me after 2 days of tries, I checked each library internally to get this solution.

Moving on to next route if some url cannot be found

I am working on an application using Laravel 5.4, VueJS, AXIOS, VueRouter. VueRouter is responsible for all of the navigation in the application, save for the route config below:
<?php
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Auth::routes();
Route::get('/{catchall?}', ['as' => 'start', 'middleware' => 'auth', function() {
return view('home')->with([...]);
}])->where('catchall', '.*');
This essentially redirects the user to the 'base' blade file, which contains the <router-link></routerlink>.
However, I am now implementing a page in my application that anyone can access (not requiring the auth middleware), and utilizes a different layout entirely (so needs a new blade file).
Preferably, it would be accessed like this:
app.sample.com/{some-string}
Rather than something else like this:
app.sample.com/survey/{some-string}
{some-string} is a randomly generated string that is then thrown into the DB. accessible by one of my models. As I understand it, I could do something like this:
Route::get('/{some-string}', 'SomeController#somemethod')
Placing this above the catchall should work. However, in the case that /{some-string} does not match anything in the DB, I would prefer that it gets handled by the catchall, which will serve the home.blade.php
Is there any clean way to handle this?
You're essentially describing the RedirectIfAuthenticated middleware, at least thats how I would approach this. Utilize middleware to check a user's authentication level and do any route matching before serving them through to the application.

Yii2 $session->setId() not working

I'm using Ajax to log in a user from subdomain. The Yii2 app is on another subdomain. Both subdomains are configured to use same cookie and session domains and save paths. I'm including session ID with Ajax call to write the user information to the same session used by non-app subdomain like this:
$session = Yii::$app->session;
$session->open();
$session->setId($post["session"]);
$session["user.id"] = $user->id;
echo $session->id; // This does not return the same ID originating from post!
Unfortunately the user information IS NOT written to the session already existing, but a new one. Is there a session involved somewhere in the middle of login process or why isn't it working? I've also tried session_id($post["session"]), but nothing.
This was actually working on previous domain, so I must be missing something. All of the AJAX posted info is correct and checked, the user is logged in properly (checked the logs) but into wrong session.
Thanks in advance!
yii\web\Session::setId() is a wrapper for session_id(), you should read PHP documentation about this function :
string session_id([ string $id ])
If id is specified, it will replace the current session id. session_id() needs to be called before session_start() for that purpose.
So you should simply try :
$session = Yii::$app->session;
$session->setId($customId);
$session->open();
I Don't think you are following the correct way to SET & GET session.
Try This:
$session = Yii::$app->session;
$session->open();
$session->set('id', $post["session"]);
echo $session->get('id');
For more info, please click Session Management - Yii2

codeigniter php native sessions without using cookies or URL session id, but matching browserfingerprints in database

Because of european privacy law being harsly applied in the Netherlands and to keep my company's site user friendly without nagging the users with questions if it's okay to store a cookie on their computer that allows me to access their client data.
What I need is a way to "overwrite" the native php sessions class so that at the point where the native class requests the cookie that stores the phpsessid, that I can place my own code there that checks the browsers fingerprint and matches that to a session id which I can use to return the normal working of the native class.
My idea is:
table sess_fingerprints
Fields: fingerprint - phpsessid
function getsessionid()
{
$result = $this->db->query("SELECT phpsessid
FROM `sessiondatabase`.`sess_fingerprints`
WHERE `sess_fingerprints`.`fingerprint` = '$userfingerprint'");
if($result->num_rows() != 0)
{
return $result->row->phpsessid;
}
}
and at that point the native php session code just works as it would normally do.
So, my question is: is it possible to overwrite only the "cookie" part of the phpsession class? if so, how? because I haven't found that yet.
I'm aware of being able to pass along the session variable via urls etc, but that's not secure enough for my web applications.
PHP provides support for custom session handlers:
http://php.net/manual/en/session.customhandler.php
I think I have found the solution to my problem.
I'm going to override the functions related to cookies by using http://php.net/manual/en/function.override-function.php
Thank you all for thinking along.

Resources