Middleware, Gates et Policies dans Laravel : qui fait quoi ?
- Le middleware : protéger l’accès à une route
- Mais le middleware ne suffit pas toujours
- Les Policies : gérer les droits sur un modèle
- Exemple de Policy
- Pourquoi utiliser une Policy ?
- Les Gates : gérer les règles plus globales
- Exemple de Gate
- Middleware, Gate ou Policy ?
- Exemple complet avec l’espace admin
- Protéger aussi les routes d’action
- Et côté Blade ?
- En résumé
- Conclusion
Quand on parle de gestion des accès dans Laravel, on tombe assez vite sur trois notions : les middlewares, les gates et les policies.
Et honnêtement, au début, on peut facilement être perdu. On a parfois l’impression que Laravel propose plusieurs outils pour faire la même chose, alors qu’en réalité, chacun a un rôle bien précis.
Pour simplifier :
- un middleware sert à protéger l’accès à une route
- une policy sert à gérer les autorisations liées au CRUD d’un modèle
- une gate sert à gérer une règle d’autorisation plus globale, qui ne concerne pas directement un modèle
Une fois qu’on comprend cette séparation, le système devient beaucoup plus clair. On va regarder cela de plus près, mais déjà il faut bien dissocier les middlewares des gates & policies.
Vous préférez une démonstration en vidéo ?
Si vous souhaitez voir ces différents mécanismes en action, j'ai réalisé une vidéo où je construis un petit espace d'administration sous Laravel afin d'illustrer concrètement l'utilisation des middlewares, des policies et des gates.
On y voit notamment :
- la protection d'un espace d'administration avec un middleware ;
- la mise en place d'une Policy pour gérer les droits CRUD d'un modèle ;
- l'utilisation d'une Gate pour une règle métier globale (accès à une fonctionnalité Premium) ;
- l'utilisation de
@can,authorize()etGate::authorize().
J'espère qu'elle vous permettra de bien visualiser les différences entre ces trois mécanismes.
Le middleware : protéger l’accès à une route
Le middleware est souvent le premier niveau de protection. Son rôle est simple : il vérifie si l’utilisateur a le droit d’accéder à une route.
Par exemple, si je veux protéger toutes les URLs qui commencent par /admin, je peux utiliser un middleware :
1Route::middleware(['auth', 'admin'])->prefix('admin')->group(function () {2 Route::get('/utilisateurs', [UserController::class, 'index']);3});
Ici, le middleware sert à répondre à une question simple :
Est-ce que cet utilisateur peut entrer dans cette zone de l’application ?
S’il n’est pas connecté, ou s’il n’a pas le bon rôle, il ne passe pas. C’est donc parfait pour protéger une zone entière : un espace admin, un espace membre, un dashboard client, etc. L'intérêt est qu'avec un middleware, on n'arrive même pas au controller.. On est redirigé tout de suite.
Mais le middleware ne suffit pas toujours
Prenons un exemple plus précis et imaginons un espace d’administration avec deux rôles :
SuperAdminManager
Les deux rôles peuvent accéder à l’URL suivante :
1/admin/utilisateurs
Donc notre middleware laisse passer les deux utilisateurs et c'est ok. Mais une fois dans la page, les droits ne sont plus forcément les mêmes :
Le Manager peut peut-être seulement consulter la liste des utilisateurs, alors que le SuperAdmin peut aussi créer, modifier ou supprimer un utilisateur.
Et là, on voit bien la limite du middleware. Il a simplement répondu à la question suivante :
Est-ce que l’utilisateur peut accéder à cette route ?
Mais maintenant, on doit répondre à une autre question :
Est-ce que cet utilisateur peut effectuer cette action précise dans la page ?
Par exemple: Oui cet utilisateur peut voir la liste des membres mais il ne peut pas avoir le bouton qui permet de changer le rôle d'un membre.
Donc le middleware laisse passer encore une fois jusqu'au controller qui affiche la vue mais cette vue doit s'afficher différemment selon qui est connecté. C’est là qu’on va utiliser une Gate ou une Policy.
Les deux servent à la même chose: **Centraliser la logique d'autorisation pour un utilisateur **à un même endroit pour avoir un code factorisé et bien rangé + avoir à dispositions plusieurs méthodes et fonctions pratiques pour nous développeurs.

Les Policies : gérer les droits sur un modèle
Une Policy sert à regrouper les règles d’autorisation liées à un modèle.
Par exemple :
- est-ce que cet utilisateur peut voir ce post ?
- est-ce qu’il peut modifier cet utilisateur ?
- est-ce qu’il peut supprimer ce produit ?
- est-ce qu’il peut créer une ressource ?
En général, dès qu’on parle d’actions CRUD sur un modèle, on utilise une Policy tout simplement. On retiens juste une règle simple :
Une Policy pour un modèle.
Par exemple, pour un modèle Post, on va créer une Post**Policy**.
1php artisan make:policy Post**Policy** --model=Post
Laravel va alors générer une classe dans app/Policies et elle contiendra plusieurs méthodes retournant un boolean pour indiquer si oui ou non, l'utilisateur connecté peut faire telle ou telle action du CRUD.
Exemple de Policy
Imaginons qu’un utilisateur puisse modifier uniquement ses propres articles.
1use App\Models\Post; 2use App\Models\User; 3 4class PostPolicy 5{ 6 public function update(User $user, Post $post): bool 7 { 8 return $user->id === $post->user_id; 9 }10}
Ici, la méthode update centralise la règle :
Un utilisateur peut modifier un post uniquement s’il en est propriétaire.
Ensuite, dans un contrôleur, on peut écrire :
1public function edit(Post $post)2{3 $this->authorize('update', $post);4 5 return view('posts.edit', compact('post'));6}
Si l’utilisateur a le droit, Laravel continue. Sinon, Laravel renvoie automatiquement une erreur 403.
Pourquoi utiliser une Policy ?
Sans Policy, on finit vite par écrire ce genre de logique partout :
1if ($user->id !== $post->user_id) {2 abort(403);3}
Puis on recommence dans un autre contrôleur, puis dans une autre action, puis dans une autre partie du projet. Le problème, c’est que la logique d’autorisation finit dispersée dans toute l’application.
Avec une Policy, la règle est centralisée. Si elle doit changer, on sait exactement où aller. C’est beaucoup plus propre, surtout quand le projet grossit.
Les Gates : gérer les règles plus globales
Les Gates servent aussi à gérer des autorisations, mais elles sont plutôt adaptées aux règles qui ne concernent pas directement le CRUD d’un modèle.
Par exemple :
- l’utilisateur a-t-il un abonnement actif ?
- lui reste-t-il assez de crédits ?
- peut-il accéder à une fonctionnalité premium ?
- peut-il accéder au dashboard global ?
- peut-il utiliser une action spécifique ?
Dans ces cas-là, on n’est pas forcément en train de dire :
Peut-il modifier ce modèle précis ?
On est plutôt sur une règle métier globale. Et là, une Gate est souvent plus adaptée.
Exemple de Gate
On peut définir une Gate dans AppServiceProvider :
1use Illuminate\Support\Facades\**Gate**;2 3public function boot(): void4{5 Gate::define('access-premium-feature', function (User $user) {6 return $user->subscription_active;7 });8}
Puis on peut l’utiliser comme ceci :
1if (Gate::allows('access-premium-feature')) {2 // L'utilisateur peut accéder à la fonctionnalité3}
Ou encore :
1Gate::authorize('access-premium-feature');
Si l’utilisateur n’a pas le droit, Laravel renvoie une erreur 403.
Middleware, Gate ou Policy ?
Encore une fois, quand on débute sur Laravel, ca peut prêter à confusion entre ces 3 outils pour gérer correctement nos accès. Pourtant c'est finalement vraiment simple si on résume ainsi:
- Si vous voulez empêcher l’accès à une route complète, utilisez un middleware.
- Si vous voulez gérer les droits liés au CRUD d’un modèle, utilisez une policy.
- Si vous voulez écrire une règle d’autorisation qui ne concerne pas directement un modèle, utilisez une gate.
Exemple complet avec l’espace admin
Reprenons notre exemple d’administration.
On a deux rôles :
SuperAdminManager
Les deux peuvent accéder à /admin/utilisateurs.
Le middleware peut donc vérifier simplement que l’utilisateur est connecté et qu’il appartient à l’espace admin.
1Route::middleware(['auth', 'admin'])->prefix('admin')->group(function () {2 Route::get('/utilisateurs', [UserController::class, 'index']);3});
À ce stade, on ne cherche pas encore à savoir si l’utilisateur peut créer, modifier ou supprimer un autre utilisateur. On vérifie seulement qu’il peut entrer dans l’espace admin.
Ensuite, dans le contrôleur ou dans les routes liées aux actions sensibles, on peut utiliser une Policy.
1public function edit(User $user)2{3 $this->authorize('update', $user);4 5 return view('admin.users.edit', compact('user'));6}
La Policy peut ensuite décider que seul un SuperAdmin peut modifier un utilisateur.
1class UserPolicy 2{ 3 public function update(User $authUser, User $user): bool 4 { 5 return $authUser->role === 'super-admin'; 6 } 7 8 public function delete(User $authUser, User $user): bool 9 {10 return $authUser->role === 'super-admin';11 }12}
Le Manager peut donc accéder à la page de liste, mais il ne peut pas modifier ou supprimer un utilisateur.
Protéger aussi les routes d’action
Il ne faut pas seulement cacher les boutons dans l’interface, il faut aussi protéger les routes qui réalisent réellement l’action.
(N'oubliez pas, la sécurité en front n'existe pas !)
Par exemple :
1Route::delete('/admin/utilisateurs/{user}', [UserController::class, 'destroy'])2 ->middleware('can:delete,user');
Ici, Laravel va utiliser la Policy pour vérifier si l’utilisateur connecté peut supprimer cet utilisateur.
C’est très pratique, parce qu’on peut réutiliser la même règle d’autorisation à plusieurs endroits :
- dans le contrôleur
- dans la route
- dans la vue
Et côté Blade ?
Les Gates et Policies servent aussi dans les vues.
Parce qu’en pratique, même si une route est protégée, on ne va pas afficher un bouton “Supprimer” à un utilisateur qui n’a pas les droits.
Laravel propose directement des directives Blade pour ça.
Avec une Policy :
1@can('update', $user)2 <a href="{{ route('admin.users.edit', $user) }}">3 Modifier4 </a>5@endcan
Si l’utilisateur n’a pas le droit de modifier ce modèle, le bouton n’apparaît pas.
Même chose pour une suppression :
1@can('delete', $user) 2 <form method="POST" action="{{ route('admin.users.destroy', $user) }}"> 3 @csrf 4 @method('DELETE') 5 6 <button type="submit"> 7 Supprimer 8 </button> 9 </form>10@endcan
Avec une Gate, le principe est le même :
1@can('access-premium-feature')2 <a href="/premium">3 Accéder à la fonctionnalité premium4 </a>5@endcan
Ou l’inverse :
1@cannot('access-premium-feature')2 <p>Cette fonctionnalité nécessite un abonnement premium.</p>3@endcannot
C’est très utile pour garder une interface cohérente avec les droits de l’utilisateur.
En résumé
Voici la manière la plus simple de retenir la différence entre les trois systèmes :
- Si vous voulez empêcher l’accès à une route ou une zone de l’application → utilisez un Middleware
- Si vous voulez gérer les droits CRUD d’un modèle → utilisez une Policy
- Si vous voulez gérer une règle métier globale → utilisez une Gate
- Si vous voulez cacher ou afficher des boutons dans Blade → utilisez
@canet@cannot
Conclusion
Middleware, Gates et Policies ne font pas la même chose.
Le middleware sert à protéger l’accès à une route ou à une zone de l’application. Les Gates et Policies servent à centraliser les règles d'autorisation. On utilise les Policies quand cela concerne le CRUD d'un modèle. Et sinon on utilise les Gates tout simplement

Rappelons nous qu'on dispose de méthodes comme can() (ou @can pour blade) qui permettent d’adapter nos controllers ou nos view à ces autorisations. Une fois cette séparation comprise, Laravel devient beaucoup plus simple à utiliser sur ces sujets.
Quand je récupère un code existant, je vais pouvoir voir en coup d'oeil le système d'autorisation du projet grâce au dossier /app/Policies... On évite les conditions partout, on centralise les règles, et on garde un code plus propre à maintenir.
Ce système proposé par Laravel est vraiment propre, et comme toujours, il reste très simple au final. Mais c'est vrai qu'on peut parfois se mélanger les pinceaux lorsqu'on débute avec le framework... Courage ! A force de pratiquer, vous allez prendre le pli sur ce sujet ;)
Source : https://youtu.be/cjXxDpKhmu0
Écrit par
Thibault ChazottesDéveloppeur Sénior Laravel et Vue.js & Formateur web - Youtube & Udemy
Tu veux commenter ? Crée un compte ou connecte-toi.
A lire
Autres articles de la même catégorie
Cast un attribut de modèle en DTO
Utilisons un DTO en tant qu’attribut afin de rendre notre code plus consistant et éviter les surprises !
Encapsuler la logique métier dans des spécifications
Voyons comment le pattern specification peut nous aider à mieux encapsuler et tester la logique de nos applications
Que cache le cookie de session de Laravel
Analysons le cookie de session d'une application Laravel et son importance dans l'authentification