How can I force all users to be logged out in a SPA? I want it so that when I deploy a new version, all users automatically get logged out.
I tried the following, but I'm not sure if it's the safest way to do it.
php artisan key:generate
If your session data is stored in the database, you need to clear the sessions table. Running a simple SQL query will solve the problem:
DELETE FROM sessions;
If you sessions are stored in files, then as #Karl suggests, you need to delete the session files from the filesystem:
rm -rf storage/framework/sessions/*
The name of the session cookie can also be changed to force all sessions to be invalid, but this is a code change rather than clearing data. The name can be updated in the cookie key in config/session.php file. This option is NOT recommended.
As you can see, the SessionGuard does a few things, and they are agnostic of the type of SESSION_DRIVER you have set in your environment.
So after reading some of the discussion on forums with Taylor and some other Laravel heavy-weights about why there isn't such a simple function, perhaps the best solution would be create a post-deploy Job or Command that you could run that would simply cycle through all users, so for whatever is Session is set. I'd try something like:
\App\User::each(function ($u) {
Auth::login($u);
Auth::logout();
});
You can destroy all the sessions. If you use Laravel Envoy to handle deployments, you can add the following line.
rm -rf storage/framework/sessions/*
If you're using the database session driver, clearing the sessions table is easy.
DB::table('sessions')->truncate();
it's really depend on with session drive you picked.
if you using file drive you can delete storage/framework/sessions
path
if you using database drive you can delete all rows of session
table,
for any other drive you can do that like others.
You can also change cookie value in app/session.php, so user will automatically logout because of cookie is not match with the config
It's worth noting that Laravel actually only stores active sessions in
the session folder; those that are 'remembered' but haven't been
actively accessing your application do not count among these sessions.
As such, just deleting the files won't finish the job.
You must also clear all Remember Tokens from the users table, as this
token is used in conjunction with the local token stored in the cookie
to re-create the session when a user reconnects.
I'm updating all user's remember tokens to blank and then flushing stored session and then modifying HomeController.
\DB::table('users')->update(array(
'remember_token' => '',
'logout_at' => Carbon::now()->toDateTimeString()));
Session::flush();
Then in HomeController modify index function
public function index()
{
if (Auth::check()) {
$token = Auth::user()->remember_token;
if ($token) {
return view('home');
} else {
return redirect('/logout');
}
} else {
return view('home');
}
}
I'd like to share another way to achieve this, if the driver used is file.
This is the "pure" php way, so it could be a helper 'flush_sessions()':
$sessions = glob(storage_path("framework/sessions/*"));
foreach($sessions as $file){
if(is_file($file))
unlink($file);
}
It is safe to use this function? PHP will keep hidden files inside given directory (.gitignore)... so, try it out, it is safe.
It is worth to mention that if you put this inside a controller method, then your session will be restored after delete the file (apparently, Laravel will try to update the file after the request ends and if it doesn't exists, will re-create it). But all other sessions (users) will be logged out. If you run this code in php artisan tinker, for example, will not keep your own session file (because artisan runs with 'array' driver).
But, that is useful in my opinion. For example: if admin user wants to logout all users except himself.
For example:
You have two sessions:
After running the function, you have only one (the user that used it):
I hope this helps someone.
//To Logout Specific user:
//$id == user id to to whom you want to logout
\DB::table('users')->where('id', $id)->update(['remember_token' => null]);
\DB::table('sessions')->where('user_id', $id)->delete();
//To Logout All Users
$sessions = glob(storage_path("framework/sessions/*"));
foreach ($sessions as $file) {
if (is_file($file))
unlink($file);
}
//$id == user id to to whom you want to logout
\DB::table('users')->update(['remember_token' => null]);
\DB::table('sessions')->truncate();
No need to use sessions table operation if you are not using database as session driver.
Related
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 building a new Laravel application (v5.4) that will run alongside (installed in the same environment) an existing PHP application that has it's own authentication system. I want the users who have successfully logged in to the existing system to be automatically authenticated in the Laravel app so they can navigate seamlessly between the applications.
My Laravel app has read-only access (through a second db connection) to the existing system's database so it can safely check that the PHP Session matches the session cookie recorded in the database and I can even pull out the user object and hand it to Auth's login() method.
I want to know the best way to put Auth into an authorised state (not guest) and where is the best place to put such code?
Options I've thunked of so far:
Middleware: Check session and call the login() method on Auth from some application-wide middleware?
Extend Illuminate/Auth/SessionGuard.php and override the attempt() method? If so, how do I tell the other parts to use my extended SessionGuard? (I suspect this was not designed to be easily overridden)
Super hacky disgusting way of dynamically setting the user's password to a random hash and then calling Auth/LoginController#login() in the background with a faked request containing that hash as the password field. (I seriously hope this doesn't end up being the most popular answer)
Some other option (?)...
Thanks in advance for your help SO community!
The solution I ran with in the end was creating a middleware that contains this:
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
if (isSet($_SESSION['intranet_user_id']) && $_SESSION['intranet_user_id']) {
// Log them in manually
$intranet_user_id = $_SESSION['intranet_user_id'];
if (!Auth::guest() && Auth::user()->getId() !== $intranet_user_id ) {
Auth::logout();
}
if (Auth::guest()) {
Auth::login( User::find($intranet_user_id), true);
}
} else {
Auth::logout();
}
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
I tried running the following code:
Session::put('progress', '5%');
dd(Session::get('progress'));
This will show '5%' in the dump.
If I rerun the same page but comment out Session::put('progress', '5%'); so that only the dd() line is called, I get a null value instead of the 5% values stored in the previous page load.
Here is my sessions config, so I know it should be storing the data:
'driver' => 'native',
'lifetime' => 120,
'expire_on_close' => false,
Why is Laravel not storing the session data across page loads?
The problem is because you are killing the script before Laravel finishes its application lifecycle, so the values put in session (but not yet stored) got killed too.
When a Laravel application lifecycle starts, any value put in Session are not yet stored until the application lifecycle ends. That is when any value put in Session will be finally/really stored.
If you check the source you will find the same aforementioned behavior:
public function put($key, $value)
{
$all = $this->all();
array_set($all, $key, $value);
$this->replace($all);
}
If you want to test it, do the following:
Store a value in session without killing the script.
Route::get('test', function() {
Session::put('progress', '5%');
// dd(Session::get('progress'));
});
Retrieve the value already stored:
Route::get('test', function() {
// Session::put('progress', '5%');
dd(Session::get('progress'));
});
Rubens Mariuzzo's answer is very good. I just want to add that if you need the data to be stored immediately you could use the save method:
Session::put('progress', '5%');
Session::save();
For me, even after data has been stored to session properly:
dd(Session::all());
returns nothing, but:
print_r(Session::all());
returns all session data!
In my case I flashed the variable in one request and then put it into session in another request (with the same name).
Unfortunatelly, terminating method went through all the previously flashed properties and cleaned my newly created session property (it was flushed in previous request so laravel thought it was no longer required and couldn't tell it was newly created). I figured it out debugging Kernel->terminateMiddleware method. You can put a breakpoint in terminating method. At some stage it reaches Store->ageFlashData. This is the method responsible for deleting my property.
I moved the session middleware
\Illuminate\Session\Middleware\StartSession::class
to the property $middleware in
app/Http/Kernel.php
In this case, you need to remove it from the web group ($middlewareGroups)
It helped me, I hope it helps you too
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.