Implémenter un guard d'authentification personnalisé
Comprenez la structure interne du système d’authentification de Laravel et apprenez à créer un guard d’authentification personnalisé en implémentant les interfaces Guard et StatefulGuard, en descendant au niveau du code source du framework.
La façade Auth est un proxy vers Illuminate\Auth\AuthManager. AuthManager gère plusieurs guards suivant un pattern de driver et instancie/cache l’instance de guard appropriée en se basant sur la configuration de config/auth.php.
// Fonctionnement interne de Auth::guard('web') (version simplifiée de AuthManager::guard())public function guard($name = null){ $name = $name ?: $this->getDefaultDriver(); return $this->guards[$name] ?? ($this->guards[$name] = $this->resolve($name));}
resolve() lit la clé driver dans le tableau guards de config/auth.php et invoque la closure de fabrique correspondante. Les drivers intégrés session et token sont enregistrés selon le même mécanisme.
Différence entre les interfaces Guard et StatefulGuard
Un guard d’authentification Laravel doit au minimum implémenter Illuminate\Contracts\Auth\Guard. S’il doit maintenir une session, il implémente StatefulGuard.
Interface Guard (Illuminate\Contracts\Auth\Guard)
interface Guard{ // Vérifie s'il existe un utilisateur authentifié public function check(); // Vérifie si l'utilisateur est invité (non authentifié) public function guest(); // Retourne l'utilisateur authentifié actuel (null si non authentifié) public function user(); // Retourne l'ID de l'utilisateur authentifié actuel public function id(); // Vérifie si les identifiants fournis sont valides (sans connexion) public function validate(array $credentials = []); // Vérifie si un utilisateur est défini (utilisable même sans connexion via setUser) public function hasUser(); // Définit manuellement l'utilisateur authentifié public function setUser(Authenticatable $user);}
StatefulGuard hérite de Guard et ajoute les méthodes nécessaires pour maintenir l’état de connexion via session ou cookies.
interface StatefulGuard extends Guard{ // Valide les identifiants et connecte l'utilisateur (avec flag remember) public function attempt(array $credentials = [], $remember = false); // Authentifie pour une seule requête sans sauvegarder la session public function once(array $credentials = []); // Connecte directement une instance d'utilisateur public function login(Authenticatable $user, $remember = false); // Connecte en spécifiant un ID public function loginUsingId($id, $remember = false); // Authentifie pour une seule requête en spécifiant un ID public function onceUsingId($id); // Vérifie si la connexion s'est faite via le cookie « rester connecté » public function viaRemember(); // Déconnexion public function logout();}
Pour un guard qui n’a pas besoin de session (authentification API, token personnalisé, etc.), il suffit d’implémenter Guard. Si vous avez besoin de session (par exemple une connexion administrateur), implémentez StatefulGuard.
Comme les méthodes check(), guest(), id(), hasUser() de l’interface Guard ont presque toujours la même implémentation, Laravel fournit le trait Illuminate\Auth\GuardHelpers. En l’utilisant, vous pouvez limiter les méthodes obligatoires à user() et validate().
// Implémentation par défaut fournie par GuardHelpers (extrait)trait GuardHelpers{ protected $user; public function check(): bool { return ! is_null($this->user()); } public function guest(): bool { return ! $this->check(); } public function id(): mixed { return $this->user()?->getAuthIdentifier(); } public function hasUser(): bool { return ! is_null($this->user); } public function setUser(Authenticatable $user): static { $this->user = $user; return $this; }}
Exemple d’implémentation d’un guard d’authentification par token API
En s’inspirant de la conception de TokenGuard, nous implémentons un guard d’authentification par token API simple. Il récupère le token depuis un en-tête de requête ou un paramètre de requête, puis résout l’utilisateur via un UserProvider.
1
Créer la classe de guard
Créez la classe de guard dans le répertoire app/Auth.
<?phpnamespace App\Auth;use Illuminate\Auth\GuardHelpers;use Illuminate\Contracts\Auth\Guard;use Illuminate\Contracts\Auth\UserProvider;use Illuminate\Http\Request;class ApiTokenGuard implements Guard{ use GuardHelpers; protected Request $request; public function __construct(UserProvider $provider, Request $request) { $this->provider = $provider; $this->request = $request; } /** * Retourne l'utilisateur actuellement authentifié */ public function user(): ?\Illuminate\Contracts\Auth\Authenticatable { // Retourne l'utilisateur mis en cache s'il existe if (! is_null($this->user)) { return $this->user; } $token = $this->getTokenForRequest(); if (empty($token)) { return null; } // Récupère l'utilisateur via UserProvider $this->user = $this->provider->retrieveByCredentials([ 'api_token' => $token, ]); return $this->user; } /** * Valide uniquement les identifiants (pas de connexion) */ public function validate(array $credentials = []): bool { if (empty($credentials['api_token'])) { return false; } return (bool) $this->provider->retrieveByCredentials($credentials); } /** * Récupère le token depuis la requête * * Ordre de priorité : en-tête Bearer → paramètre de requête → corps de la requête */ protected function getTokenForRequest(): ?string { $token = $this->request->bearerToken(); if (empty($token)) { $token = $this->request->query('api_token'); } if (empty($token)) { $token = $this->request->input('api_token'); } return $token ?: null; } /** * Remplace l'instance de requête */ public function setRequest(Request $request): static { $this->request = $request; return $this; }}
2
Enregistrer le guard dans un service provider
Enregistrez le guard avec Auth::extend() dans la méthode boot() de AppServiceProvider.
<?phpnamespace App\Providers;use App\Auth\ApiTokenGuard;use Illuminate\Contracts\Foundation\Application;use Illuminate\Support\Facades\Auth;use Illuminate\Support\ServiceProvider;class AppServiceProvider extends ServiceProvider{ public function boot(): void { Auth::extend('api-token', function (Application $app, string $name, array $config) { // Auth::createUserProvider() résout le provider défini dans la config $provider = Auth::createUserProvider($config['provider'] ?? 'users'); return new ApiTokenGuard($provider, $app->make('request')); }); }}
Auth::createUserProvider() lit la configuration providers de config/auth.php et retourne l’instance de UserProvider correspondante. Sauf si vous créez un provider personnalisé, cet appel vous permet d’utiliser le EloquentUserProvider standard.
3
Configurer le guard dans config/auth.php
Ajoutez le nouveau guard dans config/auth.php.
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], // Guard personnalisé ajouté 'api' => [ 'driver' => 'api-token', // doit correspondre au premier argument de Auth::extend() 'provider' => 'users', ],],
4
Appliquer le guard aux routes
Spécifiez le nom du guard dans le middleware auth.
Avec Auth::viaRequest(), vous pouvez définir un guard simple avec juste une closure, sans créer de classe. Utile pour prototyper ou pour des authentifications très simples.
use Illuminate\Http\Request;use App\Models\User;// Dans AppServiceProvider::boot()Auth::viaRequest('custom-token', function (Request $request): ?User { $token = $request->bearerToken(); if (empty($token)) { return null; } return User::where('api_token', $token)->first();});
Les guards définis via Auth::viaRequest() n’utilisent pas de UserProvider, donc les méthodes du provider comme retrieveById() ne fonctionneront pas. En production, il est recommandé d’utiliser un guard basé sur une classe avec Auth::extend().
Pour récupérer les informations utilisateur depuis une source autre que la base de données (API externe, LDAP, etc.), implémentez l’interface Illuminate\Contracts\Auth\UserProvider.
<?phpnamespace App\Auth;use App\Models\User;use Illuminate\Contracts\Auth\Authenticatable;use Illuminate\Contracts\Auth\UserProvider;class ApiUserProvider implements UserProvider{ public function __construct( protected string $apiBaseUrl, protected string $model = User::class, ) {} /** * Récupère un utilisateur par son ID */ public function retrieveById(mixed $identifier): ?Authenticatable { return ($this->model)::find($identifier); } /** * Récupère un utilisateur via le token « rester connecté » * Pour les guards sans session, il suffit de retourner null */ public function retrieveByToken(mixed $identifier, string $token): ?Authenticatable { return null; } /** * Met à jour le token « rester connecté » * Pour les guards sans session, on peut ne rien faire */ public function updateRememberToken(Authenticatable $user, string $token): void {} /** * Récupère un utilisateur par ses identifiants * Appelé depuis validate() et user() du guard */ public function retrieveByCredentials(array $credentials): ?Authenticatable { if (empty($credentials['api_token'])) { return null; } // Exemple : valider le token via une API externe et récupérer les infos utilisateur $response = \Illuminate\Support\Facades\Http::withToken($credentials['api_token']) ->get("{$this->apiBaseUrl}/auth/me"); if (! $response->successful()) { return null; } $data = $response->json(); // Lier avec un utilisateur en DB locale ou générer un modèle dynamiquement return User::firstOrCreate( ['external_id' => $data['id']], ['name' => $data['name'], 'email' => $data['email']], ); } /** * Valide les identifiants de l'utilisateur récupéré * Pour une authentification par token, true suffit si retrieveByCredentials a réussi */ public function validateCredentials(Authenticatable $user, array $credentials): bool { return true; } /** * Vérifie si un rehachage du mot de passe est nécessaire * Toujours false pour une authentification par token */ public function rehashPasswordIfRequired(Authenticatable $user, array $credentials, bool $force = false): void {}}
Exemple d’implémentation d’un guard personnalisé utilisant un service JWT externe.
<?phpnamespace App\Auth;use App\Models\User;use Illuminate\Auth\GuardHelpers;use Illuminate\Contracts\Auth\Guard;use Illuminate\Contracts\Auth\UserProvider;use Illuminate\Http\Request;use Illuminate\Support\Facades\Http;class JwtGuard implements Guard{ use GuardHelpers; protected Request $request; protected ?array $payload = null; public function __construct(UserProvider $provider, Request $request) { $this->provider = $provider; $this->request = $request; } public function user(): ?\Illuminate\Contracts\Auth\Authenticatable { if (! is_null($this->user)) { return $this->user; } $token = $this->request->bearerToken(); if (empty($token)) { return null; } // Vérifier le JWT via un service externe $payload = $this->verifyToken($token); if (is_null($payload)) { return null; } $this->payload = $payload; $this->user = $this->provider->retrieveById($payload['sub']); return $this->user; } public function validate(array $credentials = []): bool { if (empty($credentials['token'])) { return false; } return ! is_null($this->verifyToken($credentials['token'])); } /** * Vérification du JWT et récupération du payload */ protected function verifyToken(string $token): ?array { $response = Http::withToken($token) ->get(config('services.auth.verify_url')); if (! $response->successful()) { return null; } return $response->json(); } /** * Retourne le payload JWT vérifié */ public function payload(): ?array { return $this->payload; }}
Enregistrement dans AppServiceProvider :
Auth::extend('jwt', function (Application $app, string $name, array $config) { return new \App\Auth\JwtGuard( Auth::createUserProvider($config['provider'] ?? 'users'), $app->make('request'), );});
Vous pouvez accéder à des méthodes propres au guard personnalisé comme Auth::guard('jwt')->payload(). Auth::guard() renvoie l’instance du guard elle-même, donc les méthodes hors interface sont également invocables.