Aller au contenu principal

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 :

  1. É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).
  2. 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).
  3. 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 update avec vigilance, lecture des CVE (ex. security-advisories).
  • Secrets : jamais en dur ; .env non 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é

RisqueMesure Laravel
CSRF@csrf, middleware VerifyCsrfToken
XSS{{ }} en Blade, pas de {!! }} sur contenu utilisateur
Mots de passeHash::make(), Hash::check()
Brute-force / abusthrottle middleware, RateLimiter
AutorisationPolicies, 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).