To prevent a user from authenticating from multiple browsers or computers at the same time using Laravel Sanctum, you can implement a mechanism to track and validate the user's session based on a unique identifier that is more specific than just the IP address. One approach is to use a combination of the user's device information and a unique session token.
Here's a step-by-step solution:
-
Generate a Unique Device Identifier: When a user logs in, generate a unique identifier for the device. This can be done using a combination of user agent and a random token.
-
Store the Device Identifier: Store this identifier in the personal_access_tokens table along with the token.
-
Validate the Device Identifier on Each Request: On each request, check if the device identifier matches the one stored in the database.
-
Invalidate Previous Sessions: If a new login is detected from a different device, invalidate the previous session.
Here is a code example to illustrate this approach:
Step 1: Generate a Unique Device Identifier
You can create a middleware to generate and attach a unique device identifier to each request.
// app/Http/Middleware/GenerateDeviceIdentifier.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Str;
class GenerateDeviceIdentifier
{
public function handle($request, Closure $next)
{
if (!$request->hasHeader('Device-Identifier')) {
$deviceIdentifier = Str::random(40);
$request->headers->set('Device-Identifier', $deviceIdentifier);
}
return $next($request);
}
}
Step 2: Store the Device Identifier
Modify the Sanctum token creation process to store the device identifier.
// app/Http/Controllers/AuthController.php
use Illuminate\Http\Request;
use Laravel\Sanctum\PersonalAccessToken;
public function login(Request $request)
{
// Validate the request...
$user = User::where('email', $request->email)->first();
if ($user && Hash::check($request->password, $user->password)) {
// Generate a new token
$token = $user->createToken('auth_token')->plainTextToken;
// Store the device identifier
$deviceIdentifier = $request->header('Device-Identifier');
$tokenModel = PersonalAccessToken::findToken($token);
$tokenModel->update(['device_identifier' => $deviceIdentifier]);
return response()->json(['token' => $token]);
}
return response()->json(['message' => 'Invalid credentials'], 401);
}
Step 3: Validate the Device Identifier on Each Request
Create a middleware to validate the device identifier on each request.
// app/Http/Middleware/ValidateDeviceIdentifier.php
namespace App\Http\Middleware;
use Closure;
use Laravel\Sanctum\PersonalAccessToken;
class ValidateDeviceIdentifier
{
public function handle($request, Closure $next)
{
$token = $request->bearerToken();
$deviceIdentifier = $request->header('Device-Identifier');
if ($token) {
$tokenModel = PersonalAccessToken::findToken($token);
if ($tokenModel && $tokenModel->device_identifier !== $deviceIdentifier) {
return response()->json(['message' => 'Invalid device'], 401);
}
}
return $next($request);
}
}
Step 4: Invalidate Previous Sessions
When a new login is detected from a different device, invalidate the previous session.
// app/Http/Controllers/AuthController.php
public function login(Request $request)
{
// Validate the request...
$user = User::where('email', $request->email)->first();
if ($user && Hash::check($request->password, $user->password)) {
// Invalidate previous tokens
$user->tokens()->delete();
// Generate a new token
$token = $user->createToken('auth_token')->plainTextToken;
// Store the device identifier
$deviceIdentifier = $request->header('Device-Identifier');
$tokenModel = PersonalAccessToken::findToken($token);
$tokenModel->update(['device_identifier' => $deviceIdentifier]);
return response()->json(['token' => $token]);
}
return response()->json(['message' => 'Invalid credentials'], 401);
}
Register Middleware
Don't forget to register your middleware in app/Http/Kernel.php.
// app/Http/Kernel.php
protected $routeMiddleware = [
// ...
'generate.device.identifier' => \App\Http\Middleware\GenerateDeviceIdentifier::class,
'validate.device.identifier' => \App\Http\Middleware\ValidateDeviceIdentifier::class,
];
Apply Middleware
Apply the middleware to your routes.
// routes/api.php
Route::middleware(['auth:sanctum', 'validate.device.identifier'])->group(function () {
// Your protected routes
});
This approach ensures that each user session is tied to a specific device, and any attempt to use the same token from a different device will be rejected.