Implémenter un système de Refresh Token avec Laravel 11 et Sanctum

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

  1. POST /api/login → obtient access_token + refresh_token
  2. Utilise access_token pour appeler /api/me
  3. Quand le token expire, utilise POST /api/refresh avec le refresh_token pour obtenir un nouveau access_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.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Retour en haut