Module 18 – Sécurité Laravel
Niveau 4 – Laravel avancé (PRO)
Objectifs
Au programme :
- Comprendre et appliquer la protection CSRF dans les formulaires et les APIs
- Prévenir les XSS (échappement, Blade, Content-Security-Policy)
- Utiliser le hashing des mots de passe (bcrypt/argon) et ne jamais les stocker en clair
- Mettre en place du rate limiting pour limiter les abus (login, API)
- Renforcer l’autorisation avec des Policies avancées et des Gates
Théorie
CSRF (Cross-Site Request Forgery)
Une attaque CSRF consiste à faire exécuter une action (POST, modification, suppression) par un utilisateur connecté sans son consentement, depuis un autre site (formulaire caché, image avec URL, etc.). Laravel se défend avec un token CSRF : chaque formulaire doit contenir un token généré par la session ; le serveur rejette les requêtes POST/PUT/DELETE sans token valide.
En Blade : @csrf dans tout formulaire en méthode POST (ou PUT/DELETE avec @method).
En API (Sanctum, token) : pas de CSRF pour les requêtes avec token Bearer (stateless).
Exclusion : routes dans VerifyCsrfToken (middleware) si une route doit accepter des webhooks externes (à faire avec précaution, par ex. signature HMAC).
XSS (Cross-Site Scripting)
L’attaquant injecte du JavaScript (ou du HTML) dans une page vue par d’autres utilisateurs. Défenses :
- Échappement à l’affichage : en Blade,
{{ $var }}échappe automatiquement le HTML. Ne jamais afficher du contenu utilisateur avec{!! $var !!}sauf si c’est du contenu de confiance (ex. HTML généré par un éditeur WYSIWYG avec sanitization). - Content-Security-Policy (CSP) : en-tête HTTP qui restreint les sources de scripts, styles, images. Réduit l’impact d’une injection (ex. empêcher l’exécution de scripts inline non autorisés).
- Validation : limiter la longueur et le format des champs pour réduire la surface d’attaque.
Règle d’or : tout contenu dynamique (utilisateur, BDD) affiché dans le HTML doit être échappé, sauf cas explicite et sécurisé.
Hashing des mots de passe
Ne jamais stocker les mots de passe en clair. Laravel utilise bcrypt (ou Argon2 selon la config) via la facade Hash.
Lors de l’inscription / mise à jour :
use Illuminate\Support\Facades\Hash;
$user->password = Hash::make($request->password);
Vérification (login) :
if (Hash::check($request->password, $user->password)) {
// mot de passe correct
}
Vérifier si un rehash est nécessaire (après changement d’algorithme ou de coût) :
if (Hash::needsRehash($user->password)) {
$user->password = Hash::make($request->password);
$user->save();
}
Rate limiting
Limiter le nombre de requêtes par IP (ou par utilisateur) pour éviter le brute-force (login), le spam (formulaires) ou la surcharge des APIs.
Laravel : middleware throttle. Défini dans RouteServiceProvider ou dans les routes.
Exemple :
Route::middleware(['throttle:60,1'])->group(function () {
// 60 requêtes par minute (1 = 1 minute)
});
Route::post('/login', ...)->middleware('throttle:5,1'); // 5 tentatives par minute
API : les routes dans api.php ont par défaut un throttle (configurable dans RouteServiceProvider). On peut définir des limites nommées dans App\Providers\RouteServiceProvider avec RateLimiter::for('api', ...).
Réponse : en cas de dépassement, Laravel renvoie 429 Too Many Requests avec l’en-tête Retry-After.
Policies avancées
Les Policies définissent qui peut faire quoi sur un modèle. Cas avancés :
Autorisation selon un attribut du modèle :
public function update(User $user, Article $article): bool
{
return $user->id === $article->user_id || $user->isAdmin();
}
Politique pour un modèle non lié (ex. rapport global) :
public function viewAny(User $user): bool
{
return $user->hasRole('admin');
}
Utilisation dans le contrôleur :
$this->authorize('update', $article);
$this->authorize('create', Article::class);
Dans Blade :
@can('update', $article)
<a href="{{ route('articles.edit', $article) }}">Edit</a>
@endcan
Gates pour des règles globales (ex. « est admin ») :
Gate::define('admin', fn (User $user) => $user->role === 'admin');
// $this->authorize('admin');
Bonnes pratiques supplémentaires
- HTTPS en production (redirection HTTP → HTTPS, cookies sécurisés).
- Headers de sécurité : X-Frame-Options, X-Content-Type-Options, Referrer-Policy (middleware ou config serveur).
- Validation stricte : type des entrées, longueur max, whitelist (in:...) plutôt que blacklist.
- Dépendances :
composer updateavec vigilance, lecture des CVE (ex. security-advisories). - Secrets : jamais en dur ;
.envnon versionné ; variables d’environnement en production.
Exemples
Rate limiter personnalisé (RouteServiceProvider) :
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
Policy avec rôle :
public function delete(User $user, Article $article): bool
{
return $user->id === $article->user_id || $user->role === 'admin';
}
Résumé
| Risque | Mesure Laravel |
|---|---|
| CSRF | @csrf, middleware VerifyCsrfToken |
| XSS | {{ }} en Blade, pas de {!! }} sur contenu utilisateur |
| Mots de passe | Hash::make(), Hash::check() |
| Brute-force / abus | throttle middleware, RateLimiter |
| Autorisation | Policies, Gates, authorize() |
Quiz – Module 18
Q1. À quoi sert le token CSRF et où doit-il apparaître ?
Q2. Pourquoi ne doit-on jamais afficher du contenu utilisateur avec {!! $var !!} sans précaution ?
Q3. Quelle facade utiliser pour hasher un mot de passe et pour le vérifier ?
Q4. Comment limiter une route à 10 requêtes par minute par IP ?
Q5. Où définir la règle « un utilisateur ne peut modifier que ses propres articles » ?
Réponses
R1. À empêcher les requêtes forgées depuis un autre site (CSRF). Il doit apparaître dans tout formulaire en POST (ou PUT/DELETE) via @csrf en Blade.
R2. Parce que {!! !!} n’échappe pas le HTML : un attaquant peut injecter du JavaScript (XSS) et voler des sessions ou modifier la page.
R3. Hash::make($password) pour hasher, Hash::check($saisi, $hash) pour vérifier.
R4. En ajoutant le middleware throttle:10,1 sur la route (10 requêtes par 1 minute).
R5. Dans une Policy du modèle Article, méthode update(User $user, Article $article) qui retourne par ex. $user->id === $article->user_id.
Suite
Module 19 – Performance & optimisation (eager loading, cache, queues, jobs, events & listeners).