Objectifs de cette leçon
- Implémenter l'authentification avec Breeze/Jetstream
- Gérer les rôles et permissions
- Sécuriser les routes avec des middleware
Authentification API avec Sanctum 🔐
Pourquoi sécuriser une API ?
Une API publique expose des données et des actions. Sans authentification :
- N'importe qui peut créer, lire, modifier ou supprimer des données
- Les données sensibles (email, profil) sont accessibles à tous
- Impossible de savoir qui fait quoi
💡 Sanctum est le package d'authentification officiel de Laravel pour les API. Il utilise des tokens (jetons) plutôt que des sessions.
Installation de Sanctum
Sanctum est inclus dans Laravel 11+ par défaut. Vérifie qu'il est bien installé :
composer require laravel/sanctum php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider" php artisan migrate
Le modèle User avec HasApiTokens 🗝️
Le modèle User doit utiliser le trait HasApiTokens :
<?php namespace App\Models; use Laravel\Sanctum\HasApiTokens; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { use HasApiTokens; // 👈 Ajoute les méthodes createToken(), tokens(), etc. protected $fillable = ['name', 'email', 'password']; protected $hidden = ['password', 'remember_token']; }
1. Inscription — POST /api/register 📝
// routes/api.php Route::post('/register', [AuthController::class, 'register']);
public function register(Request $request) { $validated = $request->validate([ 'name' => 'required|string|max:255', 'email' => 'required|email|unique:users', 'password' => 'required|string|min:8|confirmed', ]); $user = User::create([ 'name' => $validated['name'], 'email' => $validated['email'], 'password' => bcrypt($validated['password']), ]); // 👇 Crée un token Sanctum $token = $user->createToken('api-token')->plainTextToken; return response()->json([ 'user' => $user, 'token' => $token, ], 201); }
Requête :
POST /api/register { "name": "Alice", "email": "alice@ex.com", "password": "secret123", "password_confirmation": "secret123" }
Réponse : 201 Created
{ "user": { "id": 1, "name": "Alice", "email": "alice@ex.com" }, "token": "1|abc123def456..." }
2. Connexion — POST /api/login 🔐
Route::post('/login', [AuthController::class, 'login']);
public function login(Request $request) { $validated = $request->validate([ 'email' => 'required|email', 'password' => 'required|string', ]); $user = User::where('email', $validated['email'])->first(); if (!$user || !Hash::check($validated['password'], $user->password)) { return response()->json([ 'message' => 'Identifiants invalides', ], 401); } // 👇 Supprime les anciens tokens (optionnel) $user->tokens()->delete(); $token = $user->createToken('api-token')->plainTextToken; return response()->json([ 'user' => $user, 'token' => $token, ]); }
Requête :
POST /api/login { "email": "alice@ex.com", "password": "secret123" }
Réponse : 200 OK
{ "user": { "id": 1, "name": "Alice", "email": "alice@ex.com" }, "token": "2|def456ghi789..." }
3. Le token côté client 💾
Le frontend (React, Vue, mobile) doit :
// Après login/register localStorage.setItem('token', response.data.token); // Dans chaque requête ultérieure fetch('/api/user', { headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token'), 'Accept': 'application/json' } });
⚠️ Sécurité : En production, préfère un cookie HTTP-only plutôt que localStorage pour éviter les attaques XSS.
4. Route protégée 🛡️
Route::middleware('auth:sanctum')->group(function () { Route::get('/user', [UserController::class, 'show']); Route::put('/user', [UserController::class, 'update']); Route::post('/logout', [AuthController::class, 'logout']); });
public function show(Request $request) { return response()->json($request->user()); }
Requête :
GET /api/user Authorization: Bearer 2|def456ghi789...
Réponse : 200 OK
{ "id": 1, "name": "Alice", "email": "alice@ex.com", "created_at": "2026-01-01T00:00:00.000000Z" }
Comment Sanctum valide le token ? 🔍
- Sanctum extrait le token de l'en-tête
Authorization: Bearer ... - Il cherche le hash du token dans la table
personal_access_tokens - Il vérifie que le token n'est pas expiré (
expires_at) - Il vérifie que le token n'est pas révoqué (
deleted_atest NULL) - Si tout va bien → l'utilisateur est authentifié
5. Déconnexion — POST /api/logout 🚪
public function logout(Request $request) { // Supprime TOUS les tokens de l'utilisateur $request->user()->tokens()->delete(); // Ou seulement le token courant : // $request->user()->currentAccessToken()->delete(); return response()->json(['message' => 'Déconnecté']); }
Requête :
POST /api/logout Authorization: Bearer 2|def456ghi789...
Réponse : 200 OK
{ "message": "Déconnecté" }
💡 Après logout, le token est supprimé de la BDD. L'utilisateur doit se reconnecter.
Capacités des tokens (Abilities) 🎯
Tu peux limiter ce qu'un token peut faire :
// À la création $token = $user->createToken('api-token', ['posts:read', 'posts:create']); // Vérification dans le contrôleur if (!$request->user()->tokenCan('posts:create')) { return response()->json(['message' => 'Action non autorisée'], 403); }
Bonnes pratiques ✅
- Toujours hasher les mots de passe avec
bcrypt()ouHash::make() - Valider les entrées avant de créer un utilisateur
- Utiliser un cookie HTTP-only plutôt que localStorage en production
- Supprimer les anciens tokens au login pour limiter les sessions actives
- Ajouter une expiration aux tokens :
createToken('api-token', ['*'], now()->addDays(7)) - Utiliser des capacités (abilities) pour le contrôle d'accès fin
- Ne jamais retourner le mot de passe dans les réponses JSON
Pièges fréquents 🚨
| Piège | Solution |
|---|---|
❌ Oublier Hash::check() | Toujours comparer les mots de passe avec Hash::check(), pas avec == |
| ❌ Stocker le token en clair dans l'URL | Toujours dans l'en-tête Authorization, jamais dans l'URL |
| ❌ Ne pas gérer l'expiration des tokens | Ajoute expires_at dans la migration Sanctum |
| ❌ Retourner le token dans les réponses GET | Le token est émis une fois (login/register), pas dans chaque réponse |
| ❌ Ignorer HTTPS | En production, force HTTPS avec ForceFirstParty ou un middleware |
Exercices pour toi 🎯
- Pratique : Crée un
AuthControllercomplet avec register, login, logout, et user. - Routes : Enregistre les routes dans
routes/api.php— login et register publiques, logout et user protégées parauth:sanctum. - Test : Utilise Postman ou curl pour tester le cycle complet : register → login → GET /api/user → logout.
- Capacités : Ajoute des abilities à ton token :
posts:read,posts:create. Vérifie-les dans le contrôleur. - Défi : Ouvre le simulateur ci-dessous. Parcours le cycle complet d'authentification : inscription → connexion → token → route protégée → déconnexion.
⚡ Simulateur — Authentification API avec Sanctum
Étape 1/7{"name":"Alice","email":"alice@ex.com","password":"secret123"}User::create($request->validated());
$token = $user->createToken('api-token')->plainTextToken;
return response()->json(['token' => $token], 201);{"token":"1|abc123..."}register() crée l'utilisateur et génère un token Sanctum. Le frontend doit stocker ce token.