Dans un environnement mobile ou SPA, les tokens d’accès expirent rapidement pour des raisons de sécurité. C’est là qu’intervient le refresh token : un jeton à longue durée de vie qui permet de régénérer un nouveau token d’accès sans demander à l’utilisateur de se reconnecter.
Dans cet article, nous allons implémenter un système de refresh token personnalisé dans Laravel 11, tout en gardant la simplicité de Sanctum.
Prérequis
- Laravel 11
- Laravel Sanctum
- Une base de données fonctionnelle
- Un outil de test API (Postman, Insomnia…)
1. Mise en place de Sanctum
Installe Sanctum si ce n’est pas déjà fait :
bash$ composer require laravel/sanctum
php artisan vendor:publish --tag=sanctum-config
php artisan migrate
Configure app/Http/Kernel.php :
use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;
'api' => [
EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
Et ajoute HasApiTokens au modèle User.
2. Migration pour les Refresh Tokens
Crée une table pour stocker les refresh tokens :
bash$ php artisan make:migration create_refresh_tokens_table
Contenu de la migration :
Schema::create('refresh_tokens', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('token')->unique();
$table->timestamp('expires_at');
$table->timestamps();
});
Puis :
bash$ php artisan migrate
3. Contrôleur d’authentification avec Refresh Token
Crée un contrôleur :
bash$ php artisan make:controller AuthController
Et ajoute les méthodes suivantes :
Méthode login avec refresh token :
use Illuminate\Support\Str;
use Carbon\Carbon;
use App\Models\RefreshToken;
public function login(Request $request)
{
$request->validate([
'email' => 'required|email',
'password' => 'required',
]);
$user = User::where('email', $request->email)->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
return response()->json(['message' => 'Identifiants invalides'], 401);
}
// Créer un access token court (ex. 15 minutes)
$accessToken = $user->createToken('access-token')->plainTextToken;
// Créer un refresh token long
$refreshToken = Str::random(64);
RefreshToken::create([
'user_id' => $user->id,
'token' => hash('sha256', $refreshToken),
'expires_at' => now()->addDays(30),
]);
return response()->json([
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
]);
}
Méthode refresh :
public function refresh(Request $request)
{
$request->validate([
'refresh_token' => 'required',
]);
$hashedToken = hash('sha256', $request->refresh_token);
$record = RefreshToken::where('token', $hashedToken)
->where('expires_at', '>', now())
->first();
if (! $record) {
return response()->json(['message' => 'Refresh token invalide ou expiré'], 401);
}
$user = $record->user;
// Supprimer les anciens tokens
$user->tokens()->delete();
$newAccessToken = $user->createToken('access-token')->plainTextToken;
return response()->json([
'access_token' => $newAccessToken
]);
}
Méthode logout :
public function logout(Request $request)
{
$request->user()->tokens()->delete();
// Supprimer les refresh tokens associés
RefreshToken::where('user_id', $request->user()->id)->delete();
return response()->json(['message' => 'Déconnexion réussie']);
}
4. Routing API
Dans routes/api.php :
use App\Http\Controllers\AuthController;
Route::post('/login', [AuthController::class, 'login']);
Route::post('/refresh', [AuthController::class, 'refresh']);
Route::middleware('auth:sanctum')->group(function () {
Route::post('/logout', [AuthController::class, 'logout']);
Route::get('/me', fn(Request $request) => $request->user());
});
5. Tester dans Postman
POST /api/login→ obtientaccess_token+refresh_token- Utilise
access_tokenpour appeler/api/me - Quand le token expire, utilise
POST /api/refreshavec lerefresh_tokenpour obtenir un nouveauaccess_token
Sécurité supplémentaire
- Chiffrement : les refresh tokens sont stockés hachés (
hash('sha256')), donc non récupérables même en cas de fuite de DB. - Expiration : tu peux fixer la durée que tu veux (7, 30, 90 jours…).
- Rotation (optionnel) : tu peux invalider le refresh token après chaque utilisation et en générer un nouveau.
Conclusion
Bien que Laravel Sanctum ne gère pas les refresh tokens nativement, il est tout à fait possible de l’étendre facilement pour avoir un système complet et sécurisé. Tu gardes la simplicité de Sanctum, tout en te rapprochant d’un vrai système d’authentification moderne utilisé en production.

