Installation
composer require ajgl/session-concurrency
(Note: This package is Symfony-focused, but can be adapted for Laravel via custom authentication logic.)
First Use Case: Basic Concurrency Check
Illuminate\Auth\SessionGuard or using middleware.use Ajgl\SessionConcurrency\Strategy\ConcurrencyControlStrategy;
use Ajgl\SessionConcurrency\Strategy\CompositeStrategy;
$concurrencyStrategy = new ConcurrencyControlStrategy($maxSessions);
$defaultStrategy = new \Symfony\Component\Security\Core\Authentication\Strategy\SessionAuthenticationStrategy();
$compositeStrategy = new CompositeStrategy([
$concurrencyStrategy,
$defaultStrategy,
]);
Where to Look First
src/Strategy/: Core strategies (e.g., ConcurrencyControlStrategy).src/EventListener/: SessionRegistryExpirationListener for session cleanup.AjglSessionConcurrencyBundle.Middleware Approach Use middleware to wrap auth checks:
namespace App\Http\Middleware;
use Ajgl\SessionConcurrency\Strategy\ConcurrencyControlStrategy;
use Closure;
class CheckSessionConcurrency
{
protected $strategy;
public function __construct(ConcurrencyControlStrategy $strategy)
{
$this->strategy = $strategy;
}
public function handle($request, Closure $next)
{
if (!$this->strategy->supports($request)) {
return $next($request);
}
if (!$this->strategy->authenticate($request)) {
return redirect()->route('login')->with('error', 'Max sessions reached.');
}
return $next($request);
}
}
Register in app/Http/Kernel.php:
protected $middlewareGroups = [
'web' => [
// ...
\App\Http\Middleware\CheckSessionConcurrency::class,
],
];
Session Storage Hooks
Extend Laravel’s SessionGuard to log sessions:
use Illuminate\Auth\SessionGuard;
use Ajgl\SessionConcurrency\SessionRegistry;
class CustomSessionGuard extends SessionGuard
{
protected $registry;
public function __construct(SessionRegistry $registry, $request)
{
$this->registry = $registry;
parent::__construct($request);
}
public function login($user)
{
$this->registry->add($this->session()->getId(), $user->id);
return parent::login($user);
}
}
Event-Driven Session Cleanup
Listen for Illuminate\Session\Events\Starting to sync sessions:
use Ajgl\SessionConcurrency\EventListener\SessionRegistryExpirationListener;
use Illuminate\Support\Facades\Event;
Event::listen('Illuminate\Session\Events\Starting', function () {
$listener = new SessionRegistryExpirationListener(
$maxSessions,
$sessionRegistry
);
$listener->onKernelResponse();
});
SESSION_DRIVER (e.g., file, database) supports session ID persistence.sessions table or cache (e.g., Redis) for performance.SessionRegistry to test concurrency logic:
$registry = $this->createMock(SessionRegistry::class);
$registry->method('countForUser')->willReturn(3);
$strategy = new ConcurrencyControlStrategy(2, $registry);
$this->assertFalse($strategy->authenticate($request));
Session ID Mismatches
session()->getId() if needed:
$sessionId = $request->session()->getId() ?: session_id();
Race Conditions
login() calls can lead to duplicate session entries. Use transactions or locks:
DB::transaction(function () use ($user, $registry) {
$registry->add($sessionId, $user->id);
});
Symfony Dependencies
Security\Core\Authentication\Token\TokenInterface. Adapt Laravel’s Authenticatable:
use Symfony\Component\Security\Core\User\UserInterface;
class LaravelUser implements UserInterface
{
// Implement Symfony's UserInterface methods.
}
Log Session Registry
Add debug output to SessionRegistry:
public function add($sessionId, $userId)
{
\Log::debug("Added session {$sessionId} for user {$userId}");
// ...
}
Check Event Listeners
Ensure SessionRegistryExpirationListener is triggered:
Event::listen('kernel.response', function () {
\Log::debug('Kernel response event fired');
});
Custom Expiration Logic
Override SessionRegistryExpirationListener to implement soft/logged-out sessions:
class CustomExpirationListener extends SessionRegistryExpirationListener
{
protected function expireOldSessions($userId, $maxSessions)
{
// Custom logic (e.g., notify user via email).
parent::expireOldSessions($userId, $maxSessions);
}
}
Dynamic Max Sessions
Fetch $maxSessions from a config or user role:
$maxSessions = config("auth.max_sessions.{$user->role}");
$strategy = new ConcurrencyControlStrategy($maxSessions, $registry);
IP/Device Fingerprinting
Enhance SessionRegistry to track devices:
public function add($sessionId, $userId, $request)
{
$this->sessions[$userId][$sessionId] = [
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
];
}
How can I help you explore Laravel packages today?