Revoke single session by id
I'm trying to implement a feature where I list out a user's sessions (I'm using the database session driver), and allow them to revoke a given session if they choose to. When I do that, I delete the session from the database, which forces a logout of that specific session if the remember cookie is not set for it. If the remember cookie is set, the session does not get logged out.
I know I could just use Auth::logoutOtherDevices('current-password'), however I'd like to be able to just revoke a single session as well if needed. I've written a solution that seems to work for what I want, however I'm not sure if there's any implications to what I'm doing in it. Any help/advice on my implementation would be greatly appreciated.
Here is my Livewire component right now for managing the user's sessions:
use Filament\Actions\Concerns\InteractsWithActions;
use Filament\Actions\Contracts\HasActions;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Livewire\Attributes\Computed;
use Livewire\Component;
//...
class SessionManager extends Component implements HasActions, HasForms
{
use InteractsWithActions;
use InteractsWithForms;
#[Computed]
public function isUsingDatabaseDriver(): bool
{
return config('session.driver') === 'database';
}
#[Computed]
public function sessions(): Collection
{
if (! $this->isUsingDatabaseDriver) {
return collect();
}
// This is based off Jetstream's implementation
return collect(
$this->sessionsDb()
->where('user_id', Filament::auth()->id())
->orderBy('last_activity', 'desc')
->get(),
)->map(function ($session) {
return (object) [
'id' => Crypt::encryptString($session->id),
'agent' => $this->createAgent($session),
'ip_address' => $session->ip_address,
'is_current_device' => $session->id === session()->getId(),
'last_active' => Date::createFromTimestamp($session->last_activity)->diffForHumans(),
];
});
}
public function revokeAction(): Action
{
return Action::make('revoke')
// ...
->form([
TextInput::make('password')
->password()
->required()
->currentPassword(),
])
->action(function (array $arguments, Form $form) {
// I encrypt the session id on the front-end
$sessionId = Crypt::decryptString($arguments['session']);
$this->deleteSessionById($sessionId, $form->getState()['password']);
// ...
})
}
public function deleteSessionById(string $sessionId, string $password): void
{
// Here's where I'm not sure if there's anything else I should be doing
Filament::auth()->user()->forceFill(['password' => $password])
->save();
session()->put([
'password_hash_web' => Filament::auth()->user()->getAuthPassword(),
]);
// Preserve login status of other sessions
$this->sessionsDb()
->where('user_id', Filament::auth()->id())
->whereNotIn('id', [session()->getId(), $sessionId])
->select(['id', 'payload'])
->cursor()
->each(function ($session) {
$payload = unserialize(base64_decode($session->payload));
$payload['password_hash_web'] = Filament::auth()->user()->getAuthPassword();
$this->sessionsDB()
->where('id', $session->id)
->update(['payload' => base64_encode(serialize($payload));
});
$this->sessionsDb()
->where('user_id', Filament::auth()->id())
->where('id', $sessionId)
->delete();
}
// ...
protected function sessionsDb(): Builder
{
return DB::connection(config('session.connection'))
->table(config('session.table', 'sessions'));
}
}
Please or to participate in this conversation.