Laravel REST API多设备用户认证及JWT令牌吊销方案咨询
Hey there! Let's walk through a practical, Laravel-native approach to handle multi-device authentication with JWT—including proper token revocation for full account bans. I’ve implemented similar setups for mobile + web APIs before, so here’s what works:
First, you’ll need a dedicated table to track active device tokens for users. Let’s call it user_device_tokens (create a model UserDeviceToken for it too). Here’s the schema:
id: Primary keyuser_id: Foreign key to youruserstabledevice_id: Unique identifier for the device (e.g., a UUID generated by the mobile/web app, or a device fingerprint)token_hash: Hashed version of the JWT (never store raw JWTs—use SHA-256 or Laravel’sHashfacade)expires_at: DateTime for token expirationis_revoked: Boolean flag to mark revoked tokenscreated_at/updated_at: Timestamps
Add the Eloquent relationship to your User model:
public function deviceTokens() { return $this->hasMany(UserDeviceToken::class); }
When a user logs in from a device, generate a JWT that includes both user_id (as the standard sub claim) and device_id (a custom claim). Then store the hashed token in your user_device_tokens table.
Example code (in an auth service or controller):
use Illuminate\Support\Facades\Hash; use Tymon\JWTAuth\Facades\JWTAuth; public function generateToken(User $user, string $deviceId) { // Generate JWT with device_id claim $token = JWTAuth::claims(['device_id' => $deviceId])->fromUser($user); // Store hashed token and device info $user->deviceTokens()->create([ 'device_id' => $deviceId, 'token_hash' => Hash::make($token), 'expires_at' => now()->addDays(7), // Adjust expiration as needed 'is_revoked' => false ]); return $token; }
Single Device Revocation
To revoke a token for a specific device, just update the is_revoked flag for that user-device pair:
public function revokeDeviceToken(User $user, string $deviceId) { $user->deviceTokens() ->where('device_id', $deviceId) ->update(['is_revoked' => true]); }
Full Account Ban
For a complete account-wide ban, combine two steps for robustness:
- Add an
is_bannedboolean field to youruserstable (quick global check) - Revoke all active tokens for the user
Example code:
public function banUser(User $user) { // Mark user as banned to block future logins $user->update(['is_banned' => true]); // Revoke all active device tokens $user->deviceTokens()->update(['is_revoked' => true]); }
Create a custom middleware to validate every incoming API request. This middleware will:
- Parse the JWT to get
user_idanddevice_id - Check if the user is banned
- Verify the token exists in
user_device_tokens, is not revoked, and hasn’t expired
Create app/Http/Middleware/CheckValidDeviceToken.php:
<?php namespace App\Http\Middleware; use Closure; use Illuminate\Support\Facades\Hash; use Tymon\JWTAuth\Facades\JWTAuth; use Tymon\JWTAuth\Exceptions\JWTException; class CheckValidDeviceToken { public function handle($request, Closure $next) { try { // Parse JWT and extract claims $token = JWTAuth::parseToken(); $payload = $token->getPayload(); $userId = $payload->get('sub'); $deviceId = $payload->get('device_id'); $rawToken = $request->bearerToken(); // Check if user is banned $user = \App\Models\User::findOrFail($userId); if ($user->is_banned) { return response()->json(['message' => 'Account has been banned'], 401); } // Validate token is active and valid $tokenRecord = $user->deviceTokens() ->where('device_id', $deviceId) ->where('is_revoked', false) ->where('expires_at', '>', now()) ->first(); if (!$tokenRecord || !Hash::check($rawToken, $tokenRecord->token_hash)) { return response()->json(['message' => 'Invalid or revoked token'], 401); } } catch (JWTException $e) { return response()->json(['message' => 'Unauthorized'], 401); } return $next($request); } }
Register the middleware in app/Http/Kernel.php:
protected $routeMiddleware = [ // ... other middleware 'auth.device' => \App\Http\Middleware\CheckValidDeviceToken::class, ];
Apply it to your API routes:
Route::middleware('auth.device')->group(function () { // Your protected API routes here });
- Clean up expired tokens: Create a Laravel scheduled task to delete records where
expires_at < now()daily—keeps your database lean. - Device metadata: Add fields like
device_name,os, orbrowsertouser_device_tokensso users can view/manage their active devices (e.g., a web dashboard to revoke old devices). - Refresh tokens: Implement a refresh token flow (store refresh tokens in the same table or a separate one) to let users get new access tokens without re-logging in.
- Avoid duplicate tokens: Before generating a new token, check if the user already has an active, unrevoked token for that device—return the existing one instead of creating a new one.
内容的提问来源于stack exchange,提问作者Tahar Moustalik

