Laravel11 マルチ認証環境でのセッション管理をカスタマイズ
はじめに
Laravelでマルチ認証(User認証&Admin認証など)を実装する際、デフォルトのセッション管理では問題が発生します。具体的には、AdminとUserの両方が同じuser_id
カラムを使用するため、Admin認証時にもuser_id
にAdminのIDが格納されてしまいます。
この記事では、この問題を解決するためのカスタムセッションハンドラーの実装方法を紹介します。
前提知識:Laravelのセッション設定
セッション設定ファイル
Laravelのセッション設定はconfig/session.php
に保存されます。デフォルトではdatabaseドライバーが使用されるようになっています。
利用可能なセッションドライバー:
-
file
- ファイルベース(開発・小規模向け) -
cookie
- クッキーベース -
database
- データベース(本番環境推奨) -
redis
- Redis(本番環境推奨) - その他:
apc
,memcached
,dynamodb
,array
デフォルトのsessionsテーブル構造
Laravelインストール時に作成されるsessions
テーブルは以下の構造になっています:
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
ご覧の通り、user_id
カラムしか存在しないため、AdminとUserを区別できません。
解決策:カスタムセッションハンドラーの実装
Step 1: admin_idカラムの追加
まず、sessions
テーブルにadmin_id
カラムを追加します。
php artisan make:migration add_admin_id_to_sessions_table --table=sessions
マイグレーションファイルの内容:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('sessions', function (Blueprint $table) {
$table->unsignedBigInteger('admin_id')->nullable()->after('user_id')->index();
});
}
public function down(): void
{
Schema::table('sessions', function (Blueprint $table) {
$table->dropColumn('admin_id');
});
}
};
php artisan migrate
Step 2: カスタムセッションハンドラーの作成
app/Http/Sessions
ディレクトリを作成し、CustomDatabaseSessionHandler.php
を作成します:
<?php
namespace App\Http\Sessions;
use Illuminate\Session\DatabaseSessionHandler;
use Illuminate\Contracts\Auth\Factory as AuthFactory;
class CustomDatabaseSessionHandler extends DatabaseSessionHandler
{
protected AuthFactory $auth;
public function __construct($connection, $table, $lifetime, $container, AuthFactory $auth)
{
parent::__construct($connection, $table, $lifetime, $container);
$this->auth = $auth;
}
/**
* セッションのデフォルトペイロードを取得
*/
protected function getDefaultPayload($data): array
{
$payload = parent::getDefaultPayload($data);
$this->setAuthenticationData($payload);
return $payload;
}
/**
* セッションIDに対する挿入処理を実行
*/
protected function performInsert($sessionId, $payload): bool
{
$this->setAuthenticationData($payload);
return parent::performInsert($sessionId, $payload);
}
/**
* セッションIDに対する更新処理を実行
*/
protected function performUpdate($sessionId, $payload): int
{
$this->setAuthenticationData($payload);
return parent::performUpdate($sessionId, $payload);
}
/**
* 認証情報をペイロードに設定
*/
protected function setAuthenticationData(array &$payload): void
{
if ($this->auth->guard('admin')->check()) {
// Admin認証済み
$payload['admin_id'] = $this->auth->guard('admin')->id();
$payload['user_id'] = null;
} elseif ($this->auth->guard('web')->check()) {
// User認証済み
$payload['user_id'] = $this->auth->guard('web')->id();
$payload['admin_id'] = null;
} else {
// 未認証
$payload['user_id'] = null;
$payload['admin_id'] = null;
}
}
}
Step 3: AppServiceProviderでの登録
app/Providers/AppServiceProvider.php
のboot
メソッドにカスタムセッションハンドラーを登録します:
<?php
namespace App\Providers;
use Illuminate\Http\Request;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Auth;
use Illuminate\Session\SessionManager;
use Illuminate\Auth\Middleware\RedirectIfAuthenticated;
use Illuminate\Contracts\Auth\Factory as AuthFactory;
use App\Http\Sessions\CustomDatabaseSessionHandler;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
// カスタムセッションハンドラーの登録
$this->app->extend('session', function (SessionManager $manager) {
$manager->extend('database', function ($app) {
$config = $app['config']['session'];
$connection = $app['db']->connection($config['connection']);
$auth = $app->make(AuthFactory::class);
return new CustomDatabaseSessionHandler(
$connection,
$config['table'],
$config['lifetime'],
$app,
$auth
);
});
return $manager;
});
// 認証後のリダイレクト処理
RedirectIfAuthenticated::redirectUsing(function (Request $request) {
if ($request->routeIs('admin.*') || Auth::guard('admin')->check()) {
return route('admin.dashboard');
}
return route('dashboard');
});
}
}
動作確認
実装完了後、以下の動作を確認できます:
-
Admin認証時:
sessions
テーブルのadmin_id
に値が設定され、user_id
はnull
-
User認証時:
sessions
テーブルのuser_id
に値が設定され、admin_id
はnull
-
未認証時: 両方とも
null
注意点とベストプラクティス
明示的なnull設定の重要性
コード内でnull
を明示的に設定している理由は、値が設定されていない場合にLaravelが予期しない動作をすることがあるためです。特にセッション管理では確実性が重要です。
パフォーマンスの考慮
このカスタマイズにより、セッション更新のたびに認証状態をチェックするオーバーヘッドが発生します。高負荷なアプリケーションでは、Redisセッションドライバーの使用を検討することをお勧めします。
セキュリティの考慮
セッションテーブルに認証情報を直接保存することで、セッション管理がより透明になりますが、同時にセッションデータの保護がより重要になります。適切なデータベースアクセス制御を確実に実装してください。
まとめ
Laravelのマルチ認証環境でのセッション管理問題を、カスタムセッションハンドラーを使用して解決する方法を紹介しました。この実装により、AdminとUserのセッションが適切に分離され、認証状態の混同を防ぐことができます。
より複雑な要件がある場合は、セッション管理ライブラリの導入や、マイクロサービス化も検討してみてください。
Discussion