I'm creating 2 projects in 2 diferent domains domain1.tld and domain2.tld.
The domain1.tld is the main event producer page and the domain2.tld is one of its events. I want to share the same sessions (they actually share the same database and the same apache server). I tried to change the session driver to "database" and create a session table, but nothing happens, if i'm log in domain1.tld nothing happens in domain2.tld.
I really have searched in the net but i have found nothing
you can't do this in your way...
when you set session, a cookie set in browser for track stored session in server side.
if you want to share session between two domain you should share cookie between to site bot you can not do it (you can do it just in sub domains of ONE domain)
but there is a little hack :
The easiest work-around is to pass login/credential information from website A to website B and have website B set a seperate cookie. For example, after logging into website A you could have them quickly redirected to website B with an encrypted querystring. Website B could then read the information, set its own cookie, and redirect the user back to site A.
It's messy but possible.
Step 1: Set Session Driver for Shared Session Data
First, set your session driver to a database or cache that is shared across both domains. Your session driver cannot be file
Step 2: Implement Cross-Domain Session IDs
Session ids are passed around by cookies in Laravel. Since your websites are on different domains the session cookie does not transfer over. One way to solve this is to append them to the query string of all your requests like so: domain2.tld/?session_token=abcds2342
Within your code there must be some login that detects a session and then query the database/cache (your session driver) for a result. If a result is found, you set the session ID manually and start the session:
session_id('abcds2342');
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
Step 2A: To do this you can implement a custom middleware that overrides StartSession. This middleware should override getSession and before it checks for session_id in cookie, check if we have a token present in the Request. Sample code below:
<?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;
}
}
Step 2B: Then create a custom service provider to override SessionServiceProvider like so:
<?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.
Step 2C: Then create your App\SessionShare model for the table to store session IDs. Also, the above code doesn't take care of checking IP address so you would have to add that in to make it secure and prevent brute force attacks
Step 2D: Oh and finally don't forget to append the get parameter for session_token for all your requests
Note that the above implementation is for a database session driver. Of course, you can do this for a cache driver too. The only thing that would change is the model implementation (step 2C) to validate the session
Related
I am implementing some custom auth functionality in to my application, which is built in Laravel v8. I have been looking at Laravel Breeze to see how it is has been implemented there.
These are the relevant functions from Laravel Breeze (https://github.com/laravel/breeze/blob/1.x/stubs/default/App/Http/Controllers/Auth/AuthenticatedSessionController.php):
class AuthenticatedSessionController extends Controller
{
/**
* Handle an incoming authentication request.
*
* #param \App\Http\Requests\Auth\LoginRequest $request
* #return \Illuminate\Http\RedirectResponse
*/
public function store(LoginRequest $request)
{
$request->authenticate();
$request->session()->regenerate();
return redirect()->intended(RouteServiceProvider::HOME);
}
/**
* Destroy an authenticated session.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\RedirectResponse
*/
public function destroy(Request $request)
{
Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}
So you will notice:
In the store() function, which is called during Login, it does $request->session()->regenerate();
In the destroy() function, which is called during Logout, it does $request->session()->invalidate();
In my application custom auth code, I have applied the same implementation in my login and logout actions.
What I have found is, when I logout, it deletes the existing session file inside storage/framework/sessions but then creates another one.
Then when I login, it creates a brand new session file. This essentially means the folder gets full of session files.
Does anyone know the reason why it is implemented this way? I would have thought logout would just delete the session file without creating a new one?
That is the normal behavior of PHP and it is not specific to Laravel.
In PHP by default, all sessions are files that are stored in a tmp directory which if you analyze the session.save_path value in php.ini file, you will see where it is. These files include the serialized session data that you access using $_SESSION keyword.
Laravel utilize basically the original session file store but acts a little bit different. Beside changing the directory they are saved, when you call the regenerate function it creates another session file and deletes the old one. You can see it the implementation Illuminate\Session\Store.php. The migrate function is used to delete the session and return new session id.
public function regenerate($destroy = false)
{
return tap($this->migrate($destroy), function () {
$this->regenerateToken();
});
}
For the invalidate function it deletes the session file actually. If you inspect the implementation, the invalidate calls for migrate method, it calls a destroy method with session id as input and this function simply deletes the session file. But soon after it deletes the file, it needs to create a new session file, why? Because the logged out user needs a new session id so we can track them.
public function invalidate()
{
$this->flush();
return $this->migrate(true);
}
Laravel Session Cleanup
Laravel has a garbage cleanup functionality which runs randomly and deletes the session files that are not valid. What does it mean randomly? Well the cleanup operation is triggered based on a randomness and traffic. So in each request Laravel checks the odd of triggering the clean or not, and the odds are 2 out of 100 by default. This means if applications receives 50 requests, there is a high chance that it will trigger this cleanup.
So if you have a high traffic, there is a high chance that the session directory will be cleared at short intervals, which is quire cool since it always makes sure that the specified directory does not get over populated when visiting users increase.
By the way if you want to act aggressively and delete on a higher chance, you can change the lottery odds in the config\session.php file:
/*
|--------------------------------------------------------------------------
| 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],
Using Laravel 5.1 & 'file' Session Driver,
I'm trying to provide facility to user to track their sessions and invalidate them anytime they wish by keeping a record of their session_id within the database. With Database, I mean, I maintain a table called user_sessions which associates user_id with their session_id (obtained by Session::getId()).
So, to invalidate Session, I tried the following code,
$sessionId = Session::getId();
Session::setId($sessionId);
Session::invalidate();
and it works perfectly fine, for the case where, where user does not uses Remember Me feature.
For the case where user uses Remember Me feature, this above code does not work, So, I additionally, tried setting remember_token field to null as specified here in this answer, but with this, all sessions of the user get destroyed including the current one.
You can simply use
use Illuminate\Support\Facades\Session;
Session::forget('YOUR_SESSION_ID');
If you want to get the current session Id:
Session::driver()->getId();
I hope it helps
Sessions are meant to be short-lived. If you want something a bit more permanent you can use some sort of a long term user settings table.
Create a table user_settings:
id (PK), user_id(FK users table), settings(BLOB?), created_at, updated_at
Add a model:
class UserSetting extends Model {
public function user() {
return $this->belongsTo(User::class);
}
}
You can also associate the user with these via :
class User extends Model {
//...
public function settings() {
$this->hasMany(UserSetting::class);
}
}
You can then get all user sessions via:
User::find($u)->settings();
When a user logs in regularly or automatically via a remember token a Login event is fired.
You can listen to this in your event service provider:
\Event::listen(\Illuminate\Auth\Events\Login::class, function ($event) {
// Here you can load the last settings in the session if you want e.g.
session(['current_settings' => $event->user->settings()->latest()->value('id') ]);
// or you can just make a new entry:
$settings = new UserSettings();
$event->user->settings()->save($settings);
session(['current_settings' => $settings->id ]);
});
Note that you will have to manually persist things that need persisting instead of just putting them in the session.
I am using GeoIP package to get the user's IP and translate it into a zipcode. I don't want to do that for every request that the user is making but rather do a one time IP to zipcode, store it into session and then when I need to use it just check if the zipcode exists inside the session.
I tried to place the code inside AppServiceProvider#boot but it does not work. It is not remembered into the session. I tried inside routes but not working as well.
edit
The code inside boot method of appserviceprovider. This is just a test.
If (! Session()->has ('zipcode'))
Session(['zipcode' => geocodeZipcode()]);
The problem is that this runs everytime since the zipcode is not persisted in the session. The if is never false from my tests so far.
Where do I need to put the code to store the zipcode into the session and have it remembered even if the user is not logged in?
I basically need something like this:
1- User accesses a page on the server for the first time (any page)
2- I get the user IP and translate it to a zipcode
3- I store the zipcode into the session
4- For every other request the user makes I check if the zipcode exists into the session. If not I execute step 2.
5- Use the zipcode for its purpose
Where should I place the step 2 and 3?
In Laravel the session is initialized via middleware, and all the middlewares execute after the service providers boot phase
This is the reason why in your service provider you can't access the session: it has not been initialized yet
You should place your steps 2 and 3 in a middleware:
class ZipCodeMiddleware
{
public function handle( Request $request, Closure $next )
{
//ZIP CODE NOT FOUND IN SESSION: CREATE IT AND STORE
if ( ! Session::has( 'zipcode' ) )
{
//get ip and translate to zip
//store zip in the session
}
//use zip code here or access it later from Session
return $next($request);
}
}
Once you've stored the zip code in the session, you can access it from a controllers directly from the session, or, you could instance a class in the middleware and re-access it later with:
//use zip code here or access it later from Session
$zipClass = new ZipClass( $zipCode );
App::instance( ZipClass::class, $zipClass );
This way you can auto-inject the ZipClass depencency in your controllers and Laravel will give you back the $zipClass instance you built previously in the middleware
Is there a way to check if a user already has a valid session on a different machine?
What I want to do is when a user logs in, destroy an other sessions which they may already have, so that if they forget to logout from a computer say on campus or at work, and then they log in at home, it will destroy those other 2 sessions so they are no longer logged in?
Facebook employs this in some way.
My only thoughts so far is something to this effect:
$user = User::find(1); // find the user
Auth::login($user); // log them in
Auth::logout(); // log them out hoping that it will destroy all their sessions on all machines
Auth::login($user); // log them in again so they have a valid session on this machine
I have not had the chance to test this, and I do not know if Auth::login($user); will destroy all sessions for that user, or only the current one.
Thanks!
You can save a session_id within a user model, so that:
When logout event is fired (auth.logout) you would clear it.
When new logging event is fired you can check if attribute session_id is not null within the user model.
If it's not - destroy previous session by:
Session::getHandler()->destroy($user->session_id);
$user->session_id = Session::getId();
Hope that would help!
I realise this is an old question, but there is now a method in laravel 5.6 that does exactly this, so it may be useful for someone coming to this later. You can also retro-fit this method to earlier versions of laravel very easily.
See the docs at https://laravel.com/docs/5.6/authentication#invalidating-sessions-on-other-devices
I had the same use case as you (log out all other devices on log-in). I overrode the default login method to add my own custom logic (first copying the default login method from vendor/laravel/framework/src/illuminate/Foundation/Auth/AuthenticatesUsers.php)
In that method, there is the line if ($this->attemptLogin($request)) - within this, before the return statement, add your call to logoutOtherDevices, as below
if ($this->attemptLogin($request)) {
//log out all other sessions
Auth::logoutOtherDevices($request->password); //add this line
return $this->sendLoginResponse($request);
}
Also ensure you have un-commented the Illuminate\Session\Middleware\AuthenticateSession middleware in your app/Http/Kernel.php, as per the docs
(note that I haven't tested the above code as I was using an older version of laravel that doesn't have this method, see below). This should work in 5.6 though.
Older Laravel versions
I was actually using laravel 5.5, so didn't have access to this handy method. Luckily, it's easy to add.
I opened a laravel 5.6 project and copied the logoutOtherDevices method from vendor/laravel/framework/src/illuminate/Auth/SessionGuard.php - for reference I have pasted below
/**
* Invalidate other sessions for the current user.
*
* The application must be using the AuthenticateSession middleware.
*
* #param string $password
* #param string $attribute
* #return null|bool
*/
public function logoutOtherDevices($password, $attribute = 'password')
{
if (! $this->user()) {
return;
}
return tap($this->user()->forceFill([
$attribute => Hash::make($password),
]))->save();
}
I then copied this into my LoginController - it could go somewhere else of your choice, but I've put it here for ease / laziness. I had to modify it slightly, as below ($this->user() becomes Auth::user())
/**
* Invalidate other sessions for the current user.
* Method from laravel 5.6 copied to here
*
* The application must be using the AuthenticateSession middleware.
*
* #param string $password
* #param string $attribute
* #return null|bool
*/
public function logoutOtherDevices($password, $attribute = 'password')
{
if (! Auth::user()) {
return;
}
return tap(Auth::user()->forceFill([
$attribute => Hash::make($password),
]))->save();
}
I can then call this method in my login method, as specified earlier in my answer, with a slight adjustment - $this->logoutOtherDevices($request->password);
If you want to test this locally, it seems to work if you open your site on a normal and an incognito window. When you log in on one, you'll be logged out on the other - though you'll have to refresh to see anything change.
I hope you will see this job:
Session::regenerate(true);
a new session_id be obtained.
This may not be the best answer, but first thing that came to my mind was lowering the timeout on the session.
In app->config->session.php there's a setting for both lifetime and expire_on_close (browser).
I'd try looking into that for now, and see if someone else comes up with something better.
I just want to know if I am able to hand over session variables from Laravel to my custom code. What I mean is: I want to handle log-in through Laravel and pass it to my profile section which is not in Laravel. Most of the routes are handled by a .htaccess file. The goal is to just login with Laravel auth and save that to $_SESSION['user'] var and redirect to /profile. Somehow I don't get that. The session name is the same in both, in Laravel's session.php's cookie name and my custom code's constant. Is there any other factor I should consider ?
Okay here's the code:
namespace Services\Session;
class OldSessionAuth
{
protected $auth;
function __construct()
{
$this->auth = \Auth::user();
}
public function setSession()
{
$_SESSION['user'] = $this->auth->toArray();
$_SESSION['auth'] = 'TRUE';
return true;
}
public function destroy()
{
session_destroy();
session_unset();
}
}
So, this is sort of my Session services, which is initialized only if it passes the Auth from the controller, Now I think I don't need to do that. so I skiped it, Basic Stuffs (Auth::Check()) really. So, I'd just do this in my login method.
$old = new Services\Session\OldSessionAuth();
$old->setSession();
return Redirect::to('/');
The home page is controlled by my custom made MVC and I want to grab the session, which in this case I can't. It shows Array(). There is no session manipulation when retrieving the session.
Laravel already has a pretty good session abstraction so I don't think you needed to use session_start(), $_SESSION etc directly. Sharing an session across two applications is a bit tricky. If you are tied to using the cookie approach, then you have to make sure that the session driver in use is the cookie one. You would also need to ensure that the restrictions on the cookie aren't such that your other application isn't being sent them by the user's browser.
By default, PHP will use a file cookie driver. In this case, what you would have to do in your other application is to read the "PHPSESSID" cookie, set the session ID using session_id() to this and only then would you have access to the session data using the $_SESSION variable in the other application.
This is all pretty hacky though. I would recommend that if you need to share sessions that you make use of a database session driver instead. This way, you are able to share arbitrary session data across applications using a standard interface. In this case, you would just read the "laravel_session" cookie instead to be able to look up the session in the database. There would be many hidden pitfalls if you then wanted to also modify this data from the other application as well though.