Custom DatabaseSessionHandler in Laravel 8

Viewed 1052

I'm trying to make a custom DatabaseSessionHandler but it doesn't work as expected. The idea is to make the table sessions polymorphic to allow the session from multiple models. (To be honest, I'm not even sure it's possible)
But even before changing the table to be polymorphic, I'm trying to add a custom driver in order to manipulate the sessions.
The issue seems that my DatabaseSessionHandler, is not correctly called when I try to sign in.

config/auth.php

'guards' => [
    'web'    => [
        'driver'   => 'custom-session',
        'provider' => 'users',
    ],
    'screen' => [
        'driver'   => 'custom-session',
        'provider' => 'screens',
    ],
],
'providers' => [
    'users'   => [
        'driver' => 'eloquent',
        'model'  => Domain\User\Models\User::class,
    ],
    'screens' => [
        'driver' => 'eloquent',
        'model'  => Domain\Screen\Models\Screen::class,
    ],
]

AuthServiceProvider.php

public function boot()
{
   //This is how the session is normally registered: https://github.com/laravel/framework/blob/8.x/src/Illuminate/Session/SessionManager.php#L83
    Session::resolved(function ($session) {
        $session->extend('screen-session', function ($app) {
            $table = $app['config']['session.table'];
            $lifetime = $app['config']['session.lifetime'];
            $connection = $app['db']->connection($app['config']['session.connection']);
            return new \Support\Session\DatabaseSessionHandler($connection, $table, $lifetime, $app);
        });
    });

    // This is how the driver "session" is normally registered: https://github.com/laravel/framework/blob/8.x/src/Illuminate/Auth/AuthManager.php#L121
    Auth::resolved(function ($auth) {
        $auth->extend('custom-session', function ($app, $name, array $config) {
            $provider = Auth::createUserProvider($config['provider']);

            $guard = new SessionGuard($name, $provider, $app->session->driver('screen-session'));

            if (method_exists($guard, 'setCookieJar')) {
                $guard->setCookieJar($this->app['cookie']);
            }

            if (method_exists($guard, 'setDispatcher')) {
                $guard->setDispatcher($this->app['events']);
            }

            if (method_exists($guard, 'setRequest')) {
                $guard->setRequest($this->app->refresh('request', $guard, 'setRequest'));
            }
            return $guard;
        });
    });

}

DatabaseSessionHandler.php
This is currently a copy/past of the existing one that is used by session except the namespace. https://github.com/laravel/framework/blob/8.x/src/Illuminate/Session/DatabaseSessionHandler.php


I don't have any error message.

When I try to sign in (auth()->guard('web')->login($user))

  • It validate the login
  • I know it uses my DatabaseSessionHandler to destroy the current session in the table.
  • The session ID is regenerated and I'm not logged in

An another scenario;

  • I try to sign in with the "remember me"
  • It validate the login
  • Somehow, the session is updated in the database with the current user_id but it doesn't seems that my DatabaseSessionHandler have been used
  • I'm logged in

Removing the screen from the guards and providers to only keep web and users, doesn't change anything.

1 Answers

Finally solved it. As always, it's a simple mistake.

I add to change in .env file

SESSION_DRIVER=screen-session

And now it's working as expected.


Now, also to answer my another question

The idea is to make the table sessions polymorphic to allow the session from multiple models. (To be honest, I'm not even sure it's possible)

From my quick tests it looks realizable. Here's what I did;

create_sessions_table.php

Schema::create('sessions', function (Blueprint $table) {
    $table->string('id')->primary();
    $table->nullableMorphs('authenticable');
    $table->string('ip_address', 45)->nullable();
    $table->text('user_agent')->nullable();
    $table->text('payload');
    $table->integer('last_activity')->index();
});

DatabaseSessionHandler.php

use Illuminate\Contracts\Auth\Guard;
use Illuminate\Session\DatabaseSessionHandler as BaseDatabaseSessionHandler;

class DatabaseSessionHandler extends BaseDatabaseSessionHandler
{

    /**
     * Get the currently authenticated user's type.
     *
     * @return mixed
     */
    protected function userType()
    {
        $user = $this->container->make(Guard::class)->user();
        return optional($user)->getMorphClass();
    }

    /**
     * Add the user information to the session payload.
     *
     * @param array $payload
     * @return $this
     */
    protected function addUserInformation(&$payload)
    {
        if ($this->container->bound(Guard::class)) {
            $payload['authenticable_id'] = $this->userId();
            $payload['authenticable_type'] = $this->userType();
        }

        return $this;
    }

}

Note: If you're using Laravel jetstream, you will probably need a custom LogoutOtherBrowserSessionsForm livewire/inertiajs component since this one is based and hardcoded with the database session and looks for user_id column

Related