Laravel Passport:如何实现Token有效期动态延长?
Got it, let's tackle this dynamic token lifecycle requirement in Laravel Passport. The goal is to have tokens expire after 1 hour of inactivity, but auto-extend their validity whenever the user makes a request. Here's a practical, step-by-step implementation:
1. Add a last_active_at Field to Track User Activity
First, we need to track when the token was last used. Let's add a timestamp column to Passport's oauth_access_tokens table:
Create a migration:
php artisan make:migration add_last_active_at_to_oauth_access_tokens_table
Update the migration file:
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up() { Schema::table('oauth_access_tokens', function (Blueprint $table) { $table->timestamp('last_active_at')->nullable(); }); } public function down() { Schema::table('oauth_access_tokens', function (Blueprint $table) { $table->dropColumn('last_active_at'); }); } };
Run the migration:
php artisan migrate
2. Initialize Token Metadata on Login
When a user logs in, generate their token and set the initial last_active_at timestamp. We'll also set a far-future expires_at value (since we'll use last_active_at to determine validity instead of the default expiration):
In your login controller (e.g., AuthController):
<?php namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Carbon\Carbon; class AuthController extends Controller { public function login(Request $request) { $credentials = $request->validate([ 'email' => 'required|email', 'password' => 'required' ]); if (!Auth::attempt($credentials)) { return response()->json(['message' => 'Invalid credentials'], 401); } $user = Auth::user(); $tokenResult = $user->createToken('API Access Token'); $token = $tokenResult->token; // Set a far-future expiration (we'll use last_active_at for actual validity) $token->expires_at = Carbon::now()->addYear(); // Record initial active time $token->last_active_at = Carbon::now(); $token->save(); return response()->json([ 'access_token' => $tokenResult->accessToken, 'token_type' => 'Bearer', 'expires_at' => Carbon::parse($token->expires_at)->toDateTimeString() ]); } }
3. Custom Token Validation for Inactivity Checks
We'll override Passport's default token validator to check if the token has been inactive for over 1 hour, and update the last_active_at timestamp on every valid request.
Create a custom validator class:
<?php namespace App\Validation; use League\OAuth2\Server\Validation\AccessTokenValidatorInterface; use League\OAuth2\Server\Exception\OAuthServerException; use Psr\Http\Message\ServerRequestInterface; use Illuminate\Support\Facades\DB; use Carbon\Carbon; class CustomAccessTokenValidator implements AccessTokenValidatorInterface { public function validateAuthorization(ServerRequestInterface $request) { // Retrieve the authenticated token from the request $token = $request->getAttribute('oauth_access_token'); if (!$token) { throw OAuthServerException::accessDenied('Invalid access token'); } // Fetch the token record from the database $tokenRecord = DB::table('oauth_access_tokens') ->where('id', $token->getIdentifier()) ->first(); // Check if token exists or has been inactive for >1 hour if (!$tokenRecord || Carbon::parse($tokenRecord->last_active_at)->addHour()->isPast()) { // Revoke the token to prevent future use DB::table('oauth_access_tokens') ->where('id', $token->getIdentifier()) ->update(['revoked' => true]); throw OAuthServerException::accessDenied('Token expired due to inactivity'); } // Update last active timestamp for valid requests DB::table('oauth_access_tokens') ->where('id', $token->getIdentifier()) ->update(['last_active_at' => Carbon::now()]); return $request; } }
4. Register the Custom Validator
Tell Laravel to use our custom validator instead of the default one. Update AuthServiceProvider:
<?php namespace App\Providers; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; use Illuminate\Support\Facades\Gate; use Laravel\Passport\Passport; use League\OAuth2\Server\Validation\AccessTokenValidatorInterface; use App\Validation\CustomAccessTokenValidator; class AuthServiceProvider extends ServiceProvider { protected $policies = [ // 'App\Models\Model' => 'App\Policies\ModelPolicy', ]; public function boot() { $this->registerPolicies(); Passport::routes(); // Bind our custom token validator $this->app->bind(AccessTokenValidatorInterface::class, CustomAccessTokenValidator::class); } }
5. Test the Flow
- When a user logs in, they get a token with
last_active_atset to now. - Every subsequent request will update
last_active_atto the current time, effectively extending the token's validity by another hour. - If the user doesn't make any requests for 1 hour, the next request will be rejected with a 401 error, and the token will be revoked.
Bonus: Clean Up Expired Tokens
To keep your database clean, you can add a scheduled task to revoke tokens that have been inactive for over 1 hour. Add this to app/Console/Kernel.php:
protected function schedule(Schedule $schedule) { $schedule->call(function () { DB::table('oauth_access_tokens') ->where('last_active_at', '<=', Carbon::now()->subHour()) ->update(['revoked' => true]); })->hourly(); }
内容的提问来源于stack exchange,提问作者mohammad albaba

