> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 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.

## Structure interne du système d'authentification Laravel

### La façade `Auth` et `AuthManager`

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`.

```php theme={null}
// 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`.

<Accordion title="Interface Guard (Illuminate\Contracts\Auth\Guard)">
  ```php theme={null}
  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);
  }
  ```
</Accordion>

<Accordion title="Interface StatefulGuard (Illuminate\Contracts\Auth\StatefulGuard)">
  `StatefulGuard` hérite de `Guard` et ajoute les méthodes nécessaires pour maintenir l'état de connexion via session ou cookies.

  ```php theme={null}
  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();
  }
  ```
</Accordion>

<Info>
  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`.
</Info>

## Implémentation d'un guard personnalisé

### Le trait `GuardHelpers`

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()`.

```php theme={null}
// 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`.

<Steps>
  <Step title="Créer la classe de guard">
    Créez la classe de guard dans le répertoire `app/Auth`.

    ```php theme={null}
    <?php

    namespace 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;
        }
    }
    ```
  </Step>

  <Step title="Enregistrer le guard dans un service provider">
    Enregistrez le guard avec `Auth::extend()` dans la méthode `boot()` de `AppServiceProvider`.

    ```php theme={null}
    <?php

    namespace 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'));
            });
        }
    }
    ```

    <Info>
      `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.
    </Info>
  </Step>

  <Step title="Configurer le guard dans config/auth.php">
    Ajoutez le nouveau guard dans `config/auth.php`.

    ```php theme={null}
    'guards' => [
        'web' => [
            'driver'   => 'session',
            'provider' => 'users',
        ],

        // Guard personnalisé ajouté
        'api' => [
            'driver'   => 'api-token', // doit correspondre au premier argument de Auth::extend()
            'provider' => 'users',
        ],
    ],
    ```
  </Step>

  <Step title="Appliquer le guard aux routes">
    Spécifiez le nom du guard dans le middleware `auth`.

    ```php theme={null}
    // routes/api.php
    use Illuminate\Support\Facades\Route;

    Route::middleware('auth:api')->group(function () {
        Route::get('/user', function () {
            return auth()->user();
        });

        Route::get('/posts', [\App\Http\Controllers\PostController::class, 'index']);
    });
    ```

    Pour utiliser un guard spécifique dans un contrôleur ou du code, appelez `Auth::guard('api')` ou `auth('api')`.

    ```php theme={null}
    $user = Auth::guard('api')->user();
    ```
  </Step>
</Steps>

## Guard simplifié via une closure

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.

```php theme={null}
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();
});
```

Configuration dans `config/auth.php` :

```php theme={null}
'guards' => [
    'api' => [
        'driver' => 'custom-token',
    ],
],
```

<Warning>
  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()`.
</Warning>

## Implémentation d'un `UserProvider` personnalisé

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`.

```php theme={null}
<?php

namespace 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 {}
}
```

### Enregistrement du `UserProvider` personnalisé

```php theme={null}
// AppServiceProvider::boot()
Auth::provider('api-user', function (Application $app, array $config) {
    return new \App\Auth\ApiUserProvider(
        config('services.auth_api.base_url'),
        $config['model'] ?? \App\Models\User::class,
    );
});
```

Ajoutez dans la section `providers` de `config/auth.php` :

```php theme={null}
'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model'  => App\Models\User::class,
    ],

    // Provider personnalisé
    'api-users' => [
        'driver' => 'api-user',
        'model'  => App\Models\User::class,
    ],
],
```

Combinez guard et provider :

```php theme={null}
'guards' => [
    'api' => [
        'driver'   => 'api-token',
        'provider' => 'api-users', // spécifie le provider personnalisé
    ],
],
```

## Cas d'usage pratiques

### Authentification multiple (guards séparés pour administrateurs et utilisateurs)

<Steps>
  <Step title="Créer le modèle Admin">
    Préparez un modèle Eloquent pour les administrateurs. Hériter de `Authenticatable` permet l'intégration avec le système `Auth`.

    ```bash theme={null}
    php artisan make:model Admin -m
    ```

    ```php theme={null}
    <?php

    namespace App\Models;

    use Illuminate\Foundation\Auth\User as Authenticatable;

    class Admin extends Authenticatable
    {
        protected $fillable = ['name', 'email', 'password'];

        protected $hidden = ['password', 'remember_token'];
    }
    ```
  </Step>

  <Step title="Configurer config/auth.php">
    ```php theme={null}
    'guards' => [
        'web' => [
            'driver'   => 'session',
            'provider' => 'users',
        ],
        'admin' => [
            'driver'   => 'session',
            'provider' => 'admins', // provider pour les admins
        ],
    ],

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model'  => App\Models\User::class,
        ],
        'admins' => [
            'driver' => 'eloquent',
            'model'  => App\Models\Admin::class, // modèle d'administrateur
        ],
    ],
    ```
  </Step>

  <Step title="Configurer les routes et middlewares">
    ```php theme={null}
    // routes/web.php

    // Routes pour utilisateurs (guard web par défaut)
    Route::middleware('auth')->group(function () {
        Route::get('/dashboard', [DashboardController::class, 'index']);
    });

    // Routes pour administrateurs (guard admin)
    Route::prefix('admin')->middleware('auth:admin')->group(function () {
        Route::get('/dashboard', [AdminDashboardController::class, 'index']);
    });
    ```
  </Step>

  <Step title="Écrire le processus de connexion en spécifiant le guard">
    ```php theme={null}
    <?php

    namespace App\Http\Controllers\Admin;

    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Auth;

    class LoginController extends Controller
    {
        public function store(Request $request)
        {
            $credentials = $request->validate([
                'email'    => 'required|email',
                'password' => 'required',
            ]);

            // Faire attempt en spécifiant explicitement le guard admin
            if (Auth::guard('admin')->attempt($credentials)) {
                $request->session()->regenerate();
                return redirect()->intended('/admin/dashboard');
            }

            return back()->withErrors(['email' => 'Les informations de connexion sont incorrectes.']);
        }

        public function destroy(Request $request)
        {
            Auth::guard('admin')->logout();
            $request->session()->invalidate();
            $request->session()->regenerateToken();

            return redirect('/admin/login');
        }
    }
    ```
  </Step>
</Steps>

### Authentification API externe via token JWT

Exemple d'implémentation d'un guard personnalisé utilisant un service JWT externe.

```php theme={null}
<?php

namespace 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` :

```php theme={null}
Auth::extend('jwt', function (Application $app, string $name, array $config) {
    return new \App\Auth\JwtGuard(
        Auth::createUserProvider($config['provider'] ?? 'users'),
        $app->make('request'),
    );
});
```

<Tip>
  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.
</Tip>

## Tests

Pour les tests unitaires d'un guard personnalisé, moquez le `UserProvider` pour vérifier le comportement du guard.

```php theme={null}
<?php

namespace Tests\Unit\Auth;

use App\Auth\ApiTokenGuard;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Http\Request;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;

class ApiTokenGuardTest extends TestCase
{
    private UserProvider&MockObject $provider;
    private ApiTokenGuard $guard;

    protected function setUp(): void
    {
        parent::setUp();

        $this->provider = $this->createMock(UserProvider::class);
    }

    public function test_user_returns_null_when_no_token(): void
    {
        $request = Request::create('/');
        $guard   = new ApiTokenGuard($this->provider, $request);

        $this->assertNull($guard->user());
    }

    public function test_user_returns_user_with_valid_bearer_token(): void
    {
        $mockUser = $this->createMock(Authenticatable::class);

        $this->provider
            ->expects($this->once())
            ->method('retrieveByCredentials')
            ->with(['api_token' => 'valid-token'])
            ->willReturn($mockUser);

        $request = Request::create('/');
        $request->headers->set('Authorization', 'Bearer valid-token');

        $guard = new ApiTokenGuard($this->provider, $request);

        $this->assertSame($mockUser, $guard->user());
    }

    public function test_check_returns_false_when_no_token(): void
    {
        $request = Request::create('/');
        $guard   = new ApiTokenGuard($this->provider, $request);

        $this->assertFalse($guard->check());
    }

    public function test_validate_returns_false_without_api_token_credential(): void
    {
        $request = Request::create('/');
        $guard   = new ApiTokenGuard($this->provider, $request);

        $this->assertFalse($guard->validate([]));
    }
}
```

Pour les tests fonctionnels avec `ActingAs`, vous pouvez définir un utilisateur sur un guard spécifique.

```php theme={null}
// Authentifier un utilisateur sur un guard spécifique pour le test
$this->actingAs($user, 'api')
    ->getJson('/api/user')
    ->assertOk();
```

## Pages associées

<Card title="Authentification (introduction)" icon="shield-check" href="/fr/authentication">
  Vérifiez les starter kits et les flux d'authentification standards.
</Card>

<Card title="Conteneur de services" icon="box" href="/fr/service-container">
  Comprenez le fonctionnement du conteneur de services utilisé lors de l'enregistrement d'un guard.
</Card>


## Related topics

- [Créer un serveur MCP avec Laravel](/fr/advanced/mcp-server.md)
- [Laravel Fortify et les starter kits](/fr/advanced/fortify.md)
- [GeneratorCommand — implémenter des commandes make: personnalisées](/fr/advanced/generator-command.md)
- [Introduction à l'authentification](/fr/authentication.md)
- [Créer un fournisseur personnalisé pour l'AI SDK](/fr/advanced/ai-sdk-custom-provider.md)
