Authentification personnalisée avec Laravel : guards, policies et gates
Bienvenue à cette leçon avancée sur l'authentification et l'autorisation dans Laravel. Si Laravel offre un système d'authentification robuste et prêt à l'emploi, les applications modernes exigent souvent une flexibilité accrue. Que ce soit pour gérer plusieurs types d'utilisateurs, des jetons API complexes ou des permissions granulaires, comprendre et personnaliser le système d'authentification de Laravel est crucial.
Dans cette leçon, nous allons plonger au cœur de trois piliers fondamentaux de la sécurité Laravel : les guards (gardes), les policies (politiques) et les gates (portes). Ces concepts vous permettront de bâtir des systèmes d'authentification et d'autorisation sur mesure, adaptés aux besoins spécifiques de vos applications backend.
Introduction à l'Authentification et l'Autorisation dans Laravel
Laravel facilite grandement la gestion des utilisateurs, de la connexion à l'enregistrement. Cependant, les cas d'usage par défaut (authentification par session web ou par jeton simple) ne suffisent pas toujours.
- Authentification : Le processus de vérification de l'identité d'un utilisateur. Qui êtes-vous ?
- Autorisation : Le processus de détermination des actions qu'un utilisateur authentifié est autorisé à effectuer. Que pouvez-vous faire ?
Laravel sépare clairement ces deux concepts et offre des outils puissants pour chacun :
- Les Guards sont principalement responsables de l'authentification. Ils définissent comment les utilisateurs sont authentifiés pour chaque requête.
- Les Policies et les Gates sont les outils d'autorisation de Laravel, permettant de contrôler l'accès aux ressources ou fonctionnalités de manière fine.
Commençons par les gardes.
Les Guards (Les Gardes) : Le Cœur de l'Authentification
Les guards sont les "gardiens" de votre application. Ils définissent comment les utilisateurs sont authentifiés et comment leur session est maintenue. Laravel est livré avec des gardes par défaut (web pour l'authentification basée sur les sessions et api pour les jetons API simples), mais vous pouvez créer vos propres gardes pour gérer des scénarios d'authentification plus complexes.
Qu'est-ce qu'un Guard ?
Un guard est un pilote (driver) qui détermine la manière dont Laravel authentifie les utilisateurs pour une requête donnée. Il gère la logique de :
- Récupération de l'utilisateur : Comment trouver l'utilisateur (par exemple, à partir d'un identifiant de session, d'un jeton d'API, etc.).
- Vérification des informations d'identification : Comment valider le mot de passe ou le jeton fourni.
- Maintien de l'état d'authentification : Comment s'assurer que l'utilisateur reste authentifié pour les requêtes suivantes.
Définir un Guard Personnalisé
Pour définir un guard personnalisé, vous devez modifier le fichier de configuration config/auth.php. C'est ici que vous déclarerez votre nouveau guard et le provider (fournisseur d'utilisateurs) qu'il utilisera.
Imaginez que vous souhaitiez authentifier des utilisateurs via des jetons d'API stockés dans la base de données, mais différemment du api guard par défaut de Sanctum/Passport (peut-être pour une intégration spécifique).
// config/auth.php
return [
'defaults' => [
'guard' => 'web', // Le guard par défaut
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token', // Ancien driver token ou Sanctum/Passport driver
'provider' => 'users',
'hash' => false,
],
// Notre guard personnalisé : 'api_token_custom'
'api_token_custom' => [
'driver' => 'api_token', // Nous allons créer ce driver
'provider' => 'users', // Utilise le même fournisseur que 'web' et 'api'
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
// Vous pourriez définir un fournisseur d'utilisateurs personnalisé ici
// 'custom_users' => [
// 'driver' => 'eloquent',
// 'model' => App\Models\CustomUser::class,
// ],
],
// ...
];
Dans cet exemple, nous avons ajouté un guard api_token_custom qui utilise un driver appelé api_token. Maintenant, nous devons créer ce driver personnalisé.
Créer un Driver de Guard Personnalisé
Un driver de guard personnalisé doit implémenter l'interface Illuminate\Contracts\Auth\Guard. Cependant, il est souvent plus simple d'étendre la classe Illuminate\Auth\GuardHelpers pour bénéficier de méthodes utilitaires.
Voici un exemple simplifié d'un driver ApiTokenGuard qui recherche l'utilisateur basé sur un jeton d'API passé dans l'en-tête Authorization: Bearer.
// app/Auth/Guards/ApiTokenGuard.php
<?php
namespace App\Auth\Guards;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Http\Request;
use Illuminate\Auth\GuardHelpers; // Utilise cette trait pour les méthodes helpers
class ApiTokenGuard implements Guard
{
use GuardHelpers; // Permet d'implémenter facilement des méthodes comme user(), check(), guest()
/**
* The user provider implementation.
*
* @var \Illuminate\Contracts\Auth\UserProvider
*/
protected $provider;
/**
* The request instance.
*
* @var \Illuminate\Http\Request
*/
protected $request;
/**
* Create a new authentication guard.
*
* @param \Illuminate\Contracts\Auth\UserProvider $provider
* @param \Illuminate\Http\Request $request
* @return void
*/
public function __construct(UserProvider $provider, Request $request)
{
$this->provider = $provider;
$this->request = $request;
}
/**
* Get the currently authenticated user.
*
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user()
{
// Si l'utilisateur est déjà chargé, le retourner
if (! is_null($this->user)) {
return $this->user;
}
// Récupérer le jeton de l'en-tête Authorization
$token = $this->request->bearerToken();
if (! $token) {
return null;
}
// Tenter de récupérer l'utilisateur via le fournisseur
// Supposons que votre modèle User a une colonne 'api_token'
$user = $this->provider->retrieveByToken(null, $token);
return $this->user = $user;
}
/**
* Validate a user's credentials.
*
* @param array $credentials
* @return bool
*/
public function validate(array $credentials = [])
{
// Ce guard ne gère pas les identifiants classiques (email/password)
// mais plutôt un jeton. Vous pouvez l'adapter si besoin.
return false;
}
// Les autres méthodes (attempt, check, guest, id, setUser) sont fournies par GuardHelpers
// ou ne sont pas nécessaires pour un guard basé sur jeton simple.
}
Enregistrer le Driver du Guard
Pour que Laravel connaisse votre nouveau driver, vous devez l'enregistrer dans la méthode boot de votre App\Providers\AuthServiceProvider.
// app/Providers/AuthServiceProvider.php
<?php
namespace App\Providers;
use App\Auth\Guards\ApiTokenGuard;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Auth;
class AuthServiceProvider extends ServiceProvider
{
/**
* The model to policy mappings for the application.
*
* @var array<class-string, class-string>
*/
protected $policies = [
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
// Enregistrer notre driver de guard personnalisé
Auth::extend('api_token', function ($app, $name, array $config) {
// Retourne une instance de votre guard
return new ApiTokenGuard(Auth::createUserProvider($config['provider']), $app['request']);
});
}
}
Utilisation du Guard Personnalisé
Une fois le guard défini et enregistré, vous pouvez l'utiliser :
- Dans les routes : En spécifiant le middleware
authavec le nom de votre guard.// routes/api.php use Illuminate\Support\Facades\Route; Route::middleware('auth:api_token_custom')->group(function () { Route::get('/profile', function () { return request()->user(); }); }); - Dans le code : Pour vérifier l'authentification ou récupérer l'utilisateur via ce guard.
// Dans un contrôleur ou service use Illuminate\Support\Facades\Auth; class MyController extends Controller { public function getUserProfile() { if (Auth::guard('api_token_custom')->check()) { $user = Auth::guard('api_token_custom')->user(); // ... } // ... } }
Les guards personnalisés offrent une flexibilité incroyable pour intégrer des systèmes d'authentification tiers ou gérer des logiques d'accès complexes.
Les Policies (Les Politiques) : Autorisation Spécifique aux Modèles
Les policies sont des classes qui organisent la logique d'autorisation pour un modèle Eloquent spécifique. Elles permettent de définir des règles claires sur ce qu'un utilisateur peut faire ou ne peut pas faire avec une instance donnée d'un modèle.
Quand utiliser les Policies ?
Utilisez les policies lorsque vous avez des règles d'autorisation complexes qui se rapportent directement aux actions CRUD (Create, Read, Update, Delete) ou à d'autres actions spécifiques sur un modèle. Par exemple :
- Un utilisateur ne peut modifier que ses propres articles de blog.
- Seuls les administrateurs peuvent supprimer n'importe quel commentaire.
- Un utilisateur ne peut voir un projet que s'il en est membre.
Créer une Policy
Vous pouvez générer une policy avec la commande Artisan :
php artisan make:policy PostPolicy --model=Post
Ceci créera un fichier app/Policies/PostPolicy.php avec des méthodes pré-remplies.
// app/Policies/PostPolicy.php
<?php
namespace App\Policies;
use App\Models\User;
use App\Models\Post;
use Illuminate\Auth\Access\Response;
class PostPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
// Un utilisateur authentifié peut voir tous les articles
return $user !== null;
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Post $post): bool
{
// Tout le monde peut voir un article
return true;
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
// Seuls les utilisateurs authentifiés peuvent créer des articles
return $user !== null;
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Post $post): bool
{
// Un utilisateur peut modifier son propre article
return $user->id === $post->user_id;
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Post $post): bool
{
// Un utilisateur peut supprimer son propre article
return $user->id === $post->user_id;
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Post $post): bool
{
// Exemple : Seuls les administrateurs peuvent restaurer
return $user->isAdmin(); // Assumons une méthode isAdmin() sur le modèle User
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Post $post): bool
{
// Seuls les super-administrateurs peuvent forcer la suppression
return $user->isSuperAdmin();
}
/**
* Gère toutes les autorisations.
* Cette méthode est appelée avant toute autre méthode de la policy.
* Si elle retourne true, l'action est autorisée sans vérifier les autres méthodes.
* Si elle retourne false, l'action est refusée sans vérifier les autres méthodes.
* Si elle retourne null, la vérification continue avec la méthode spécifique.
*/
public function before(User $user, string $ability): ?bool
{
if ($user->isAdmin()) {
return true; // Les administrateurs peuvent tout faire (sauf les actions spécifiées par une autre policy)
}
return null; // La vérification continue
}
}
Enregistrer la Policy
Pour que Laravel sache quelle policy utiliser pour quel modèle, vous devez enregistrer le mapping dans AuthServiceProvider :
// app/Providers/AuthServiceProvider.php
<?php
namespace App\Providers;
use App\Models\Post;
use App\Policies\PostPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The model to policy mappings for the application.
*
* @var array<class-string, class-string>
*/
protected $policies = [
Post::class => PostPolicy::class, // Enregistrement de la policy pour le modèle Post
];
/**
* Register any authentication / authorization services.
*/
public function boot(): void
{
$this->registerPolicies();
// ...
}
}
Utilisation des Policies
Laravel offre plusieurs manières d'utiliser les policies :
-
Via l'utilisateur authentifié :
// Dans un contrôleur ou une vue if (auth()->user()->can('update', $post)) { // L'utilisateur peut mettre à jour cet article } -
Via la facade
Gate(en fait, la policy est un type de gate enregistré implicitement) :use Illuminate\Support\Facades\Gate; if (Gate::allows('update', $post)) { // L'utilisateur peut mettre à jour cet article } -
Dans les contrôleurs (méthode
authorize) : La méthodeauthorized'un contrôleur ouBaseControllerde Laravel permet de lancer une exceptionAuthorizationExceptionsi l'utilisateur n'est pas autorisé.// app/Http/Controllers/PostController.php use App\Models\Post; use Illuminate\Http\Request; class PostController extends Controller { public function update(Request $request, Post $post) { // Lance une exception 403 si l'utilisateur n'est pas autorisé $this->authorize('update', $post); // ... logique de mise à jour si autorisé ... $post->update($request->all()); return redirect()->route('posts.show', $post); } public function create() { $this->authorize('create', Post::class); // Vérifie la capacité de créer un Post (pas une instance spécifique) // ... afficher le formulaire de création ... } }Note : Lorsque vous utilisez
$this->authorize('create', Post::class);, vous passez le nom de la classe du modèle plutôt qu'une instance spécifique, ce qui indique à Laravel de vérifier la méthodecreatede laPostPolicy. -
Via le middleware
candans les routes :// routes/web.php use App\Models\Post; Route::put('/posts/{post}', function (Post $post) { // Le middleware 'can' va automatiquement vérifier la policy 'update' pour le modèle Post // en utilisant l'utilisateur authentifié et l'instance $post. })->middleware('can:update,post'); Route::get('/posts/create', function () { // Vérifie si l'utilisateur peut créer n'importe quel Post })->middleware('can:create,App\Models\Post');
Les policies sont l'approche recommandée pour gérer l'autorisation liée à des modèles Eloquent, car elles centralisent la logique et améliorent la lisibilité et la maintenabilité de votre code.
Les Gates (Les Portes) : Autorisation Générique
Les gates sont des fermetures (closures) simples ou des méthodes de rappel qui définissent une capacité (ability) spécifique dans votre application. Contrairement aux policies qui sont liées à un modèle, les gates sont plus génériques et peuvent être utilisés pour des autorisations qui ne sont pas directement liées à une instance de modèle.
Quand utiliser les Gates ?
Utilisez les gates pour :
- Permissions générales : Par exemple,
edit-settings,view-admin-dashboard. - Actions non liées à un modèle spécifique : Accéder à un rapport, gérer des utilisateurs (sans passer une instance
Userau gate), etc. - Permissions plus simples qui ne justifient pas une policy entière.
Définir un Gate
Les gates sont généralement définis dans la méthode boot de votre App\Providers\AuthServiceProvider.
// app/Providers/AuthServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use App\Models\User;
class AuthServiceProvider extends ServiceProvider
{
// ...
/**
* Register any authentication / authorization services.
*/
public function boot(): void
{
$this->registerPolicies();
// Définition d'un gate 'manage-users'
Gate::define('manage-users', function (User $user) {
return $user->hasRole('admin') || $user->hasRole('moderator');
});
// Définition d'un gate 'view-reports' qui prend un argument supplémentaire
Gate::define('view-reports', function (User $user, string $reportType) {
return $user->hasPermissionTo('view-' . $reportType . '-reports');
});
// Définition d'un gate 'before' pour les gates (similaire au before des policies)
Gate::before(function (User $user, string $ability) {
if ($user->isSuperAdmin()) {
return true; // Les super-administrateurs peuvent tout faire
}
});
}
}
Dans cet exemple :
- Le gate
manage-usersvérifie si l'utilisateur a le rôleadminoumoderator. La méthodehasRoleserait une méthode personnalisée sur votre modèleUser. - Le gate
view-reportsprend un argument supplémentaire$reportTypepour une logique plus dynamique. Gate::beforeest similaire à la méthodebeforedes policies ; elle est exécutée avant tout autre gate et peut court-circuiter la vérification.
Utilisation des Gates
Les gates peuvent être utilisés de manière similaire aux policies :
-
Via l'utilisateur authentifié :
// Dans un contrôleur ou une vue if (auth()->user()->can('manage-users')) { // L'utilisateur peut gérer les utilisateurs } if (auth()->user()->can('view-reports', 'sales')) { // L'utilisateur peut voir les rapports de ventes } -
Via la facade
Gate:use Illuminate\Support\Facades\Gate; if (Gate::allows('manage-users')) { // L'utilisateur peut gérer les utilisateurs } if (Gate::denies('manage-users')) { // L'utilisateur n'a pas la permission de gérer les utilisateurs } -
Dans les contrôleurs (méthode
authorize) :// app/Http/Controllers/UserController.php use Illuminate\Http\Request; class UserController extends Controller { public function index() { $this->authorize('manage-users'); // Vérifie le gate 'manage-users' // ... logique pour afficher la liste des utilisateurs ... } public function showSalesReports() { $this->authorize('view-reports', 'sales'); // Vérifie le gate 'view-reports' avec l'argument 'sales' // ... logique pour afficher les rapports de ventes ... } } -
Via le middleware
candans les routes :// routes/web.php Route::get('/admin/users', function () { // })->middleware('can:manage-users'); Route::get('/reports/sales', function () { // Notez l'argument passé au gate via le middleware })->middleware('can:view-reports,sales');
Les gates sont parfaits pour les autorisations légères et globales, ou lorsque la logique de permission ne dépend pas directement d'une instance de modèle spécifique.
Interconnexion et Meilleures Pratiques
Il est essentiel de comprendre quand utiliser chaque composant pour construire un système d'authentification et d'autorisation cohérent et maintenable.
Guards vs. Policies vs. Gates : Un Résumé
| Composant | Rôle Principal | Quand l'utiliser ? | Exemple | | :-------- | :------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------- | | Guards | Authentification : Qui est l'utilisateur ? | Pour définir comment un utilisateur est authentifié (session, jeton API, JWT, etc.). Permet des méthodes de connexion variées. | Authentification via jeton d'API personnalisé. | | Policies | Autorisation : Que peut faire cet utilisateur sur cette instance de modèle ? | Lorsque les règles d'autorisation sont complexes et directement liées aux actions (CRUD) sur un modèle Eloquent spécifique. | Un utilisateur peut modifier son propre article. | | Gates | Autorisation : Que peut faire cet utilisateur en général ? | Pour des permissions globales, des actions non liées à un modèle spécifique, ou des permissions simples. | Un utilisateur peut accéder au tableau de bord admin. |
Quand utiliser quoi ?
- Quel est le type d'utilisateur ? Si vous avez besoin de gérer différentes façons pour les utilisateurs de se connecter (par exemple, utilisateurs web classiques, utilisateurs API via jetons, utilisateurs de micro-services via JWT), c'est un travail pour les Guards.
- L'autorisation concerne-t-elle une instance spécifique d'un modèle Eloquent ? Par exemple, "L'utilisateur X peut-il modifier l'article Y ?" Si oui, utilisez une Policy.
- L'autorisation concerne-t-elle une capacité générale ou une action non liée à une instance de modèle ? Par exemple, "L'utilisateur X peut-il accéder à la page d'administration ?" ou "L'utilisateur X peut-il créer un article (sans se soucier d'un article spécifique) ?" Si oui, utilisez un Gate.
Il est fréquent d'utiliser une combinaison des trois. Par exemple, un guard peut authentifier un utilisateur via un jeton JWT. Une fois authentifié, des policies peuvent déterminer si cet utilisateur peut modifier un Post spécifique, et des gates peuvent vérifier si l'utilisateur a le droit d'accéder à la page de gestion des utilisateurs.
Avantages de la Personnalisation
- Flexibilité : Adaptez le système d'authentification à tous les besoins de votre application.
- Sécurité : Contrôlez précisément qui a accès à quoi, réduisant les vulnérabilités.
- Maintenabilité : Centralisez la logique d'autorisation dans des classes dédiées (policies, gates), rendant le code plus lisible et facile à modifier.
- Testabilité : Les policies et gates sont des classes distinctes qui peuvent être testées indépendamment.
Considérations de Sécurité
- Toujours valider côté serveur : Ne vous fiez jamais uniquement à la validation côté client pour l'autorisation.
- Principe du moindre privilège : Donnez aux utilisateurs uniquement les permissions dont ils ont besoin.
- Journalisation : Envisagez de journaliser les tentatives d'accès non autorisées.
Conclusion
Maîtriser les guards, les policies et les gates est une compétence essentielle pour tout développeur Laravel avancé. Ils constituent les piliers d'un système d'authentification et d'autorisation robuste, flexible et sécurisé.
En utilisant les guards pour définir comment les utilisateurs s'authentifient, les policies pour gérer les autorisations spécifiques aux modèles, et les gates pour les permissions génériques, vous pouvez construire des applications backend Laravel qui répondent précisément à vos exigences de sécurité et d'accès.
N'oubliez jamais que la sécurité est un processus continu. L'architecture de Laravel vous fournit les outils, mais c'est à vous de les implémenter judicieusement pour protéger vos données et vos utilisateurs.