Laravel shared cookie detection issue in domain and subdomain - laravel
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.
Related
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
Session Cookie - Magento login not working if the Shop is at the root domain
I had a strange Problem. After transferring a shop to the live domain xyz.tld, users and Admins couldn't login. The cookie domain was set to .xyz.tld (or xyz.tld) without difference. Not setting the cookie domain allows to login, but this leads to the problem that sometimes two cookies with the same name and different subdomain are stored. Also, without cookie domain, login in Safari is not possible at all.
The problem is a check for cookies from a parent domain in magento. If the shop is at your root domain (2nd level domain) and the cookie domain is set to the same, magento always believes every cookie is from a parent domain and deletes every session right after creating it. The fix is easy! In the File app/code/core/Mage/Core/Model/Session/Abstract.php find: // Delete cookies with the same name for parent domains if (strpos($currentCookieDomain, $host) > 0) { $this->getCookie()->delete($this->getSessionName(), null, $host); } and replace it with: // Delete cookies with the same name for parent domains if (strpos($currentCookieDomain, $host) > 1) { $this->getCookie()->delete($this->getSessionName(), null, $host); } This won't create any new problems, because no subdomain exists that is one char longer than the domain. Every subdomain must at least include one letter and the dot to separate it from the domain. The only thing that is one char longer than xyz.tld is either a different domain, or .xyz.tld. I opened Magento Issue #7015 for this bug, so this should be fixed in an upcoming version! update: The bug is still open (02/2016). Someone posted a different fix in the comments to the bug report, you might want to check it out too: https://gist.github.com/piotrekkaminski/14f49b6ddcb69640d697
Laravel: share session data over multiple domains
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.
Symfony2 A session had already been started - ignoring session_start()
I can't use Symfony2 session in my local server. I'm getting a "Notice: A session had already been started - ignoring session_start()" error. Same script works fine in my production server. I'm using Xampp with PHP 5.3.5 over Windows 7. Session auto_start is off in php.ini. Any hint will be helpfull. Thanks
I guess it's a bite late but if it can help: Make sure your session.autostart is turned off (0) in your php.ini The way to use the session in Symfony 2 from the controler is the following: $session = $this->getRequest()->getSession(); // store an attribute for reuse during a later user request $session->set('foo', 'bar'); // in another controller for another request $foo = $session->get('foo'); // use a default value if the key doesn't exist $filters = $session->get('filters', array()); http://symfony.com/doc/current/book/controller.html#managing-the-session Or from the view: {{ app.session.get('foo') }} You should also call start() even if it is automatically called when you read/write session data (because it is recommended and getId() doesn't call it for example) $session->start(); $id = $session->getId(); http://symfony.com/doc/master/components/http_foundation/sessions.html The reason you may not get the error on the production server is because it's priority is 'Notice' only.
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.