Implementación de un guard de autenticación personalizado
Aprende cómo está estructurado internamente el sistema de autenticación de Laravel y cómo crear un guard personalizado implementando las interfaces Guard y StatefulGuard, explicado a nivel de código fuente del framework.
La facade Auth es un proxy de Illuminate\Auth\AuthManager. AuthManager gestiona varios guards siguiendo el patrón driver y crea/cachea la instancia de guard correspondiente basándose en la configuración de config/auth.php.
// Funcionamiento interno de Auth::guard('web') (versión simplificada de AuthManager::guard())public function guard($name = null){ $name = $name ?: $this->getDefaultDriver(); return $this->guards[$name] ?? ($this->guards[$name] = $this->resolve($name));}
resolve() lee la clave driver desde el array guards de config/auth.php e invoca la factory closure correspondiente. Los drivers integrados session y token también están registrados mediante este mismo mecanismo.
Diferencia entre las interfaces Guard y StatefulGuard
Un guard de autenticación de Laravel debe implementar como mínimo Illuminate\Contracts\Auth\Guard. Si necesita mantener sesión, implementa StatefulGuard.
Interfaz Guard (Illuminate\Contracts\Auth\Guard)
interface Guard{ // Comprueba si hay un usuario autenticado public function check(); // Comprueba si el visitante es invitado (no autenticado) public function guest(); // Devuelve el usuario autenticado actual (null si no lo hay) public function user(); // Devuelve el ID del usuario autenticado actual public function id(); // Comprueba si las credenciales indicadas son válidas (sin hacer login) public function validate(array $credentials = []); // Comprueba si hay un usuario establecido (aunque no haya login, se puede inyectar con setUser) public function hasUser(); // Establece manualmente el usuario autenticado public function setUser(Authenticatable $user);}
StatefulGuard extiende Guard y añade los métodos necesarios para mantener el estado de login mediante sesiones y cookies.
interface StatefulGuard extends Guard{ // Verifica credenciales y hace login (con opción de "recordarme") public function attempt(array $credentials = [], $remember = false); // Autentica solo para una petición, sin guardar en sesión public function once(array $credentials = []); // Inicia sesión directamente con una instancia de usuario public function login(Authenticatable $user, $remember = false); // Inicia sesión indicando un ID public function loginUsingId($id, $remember = false); // Autentica una única petición indicando un ID public function onceUsingId($id); // Comprueba si el login se realizó a través de la cookie "recordarme" public function viaRemember(); // Cierra la sesión public function logout();}
Los guards que no requieren sesión (autenticación de API o mediante tokens propios) solo necesitan implementar Guard. Si necesitas sesión, como en el login de administradores, implementa StatefulGuard.
Como los métodos check(), guest(), id() y hasUser() de la interfaz Guard suelen tener casi la misma implementación, Laravel proporciona el trait Illuminate\Auth\GuardHelpers. Con él, los únicos métodos que quedan obligatorios son user() y validate().
// Implementación por defecto que provee GuardHelpers (extracto)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; }}
Ejemplo de guard de autenticación por token de API
Vamos a implementar un guard sencillo por token de API inspirado en el diseño de TokenGuard. Recupera el token desde una cabecera o un parámetro de la petición y resuelve el usuario a través del UserProvider.
1
Crea la clase del guard
Crea el guard en el directorio 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; } /** * Devuelve el usuario autenticado actual */ public function user(): ?\Illuminate\Contracts\Auth\Authenticatable { // Si ya hay un usuario cacheado, devuélvelo if (! is_null($this->user)) { return $this->user; } $token = $this->getTokenForRequest(); if (empty($token)) { return null; } // Obtiene el usuario a través del UserProvider $this->user = $this->provider->retrieveByCredentials([ 'api_token' => $token, ]); return $this->user; } /** * Solo valida las credenciales (no realiza el login) */ public function validate(array $credentials = []): bool { if (empty($credentials['api_token'])) { return false; } return (bool) $this->provider->retrieveByCredentials($credentials); } /** * Obtiene el token desde la petición * * Prioridad: cabecera Bearer → query param → body de la petición */ 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; } /** * Sustituye la instancia de request */ public function setRequest(Request $request): static { $this->request = $request; return $this; }}
2
Registra el guard en un service provider
Registra el guard con Auth::extend() en el método 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() resuelve el provider indicado en la config $provider = Auth::createUserProvider($config['provider'] ?? 'users'); return new ApiTokenGuard($provider, $app->make('request')); }); }}
Auth::createUserProvider() lee la configuración providers de config/auth.php y devuelve la instancia de UserProvider correspondiente. Salvo que crees tu propio provider, esta llamada te permite reutilizar el EloquentUserProvider estándar.
3
Configura el guard en config/auth.php
Añade el nuevo guard en config/auth.php.
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], // Guard personalizado añadido 'api' => [ 'driver' => 'api-token', // coincidir con el primer argumento de Auth::extend() 'provider' => 'users', ],],
Con Auth::viaRequest() puedes definir un guard simple usando solo una closure, sin crear una clase. Es adecuado para prototipos o autenticaciones muy sencillas.
use Illuminate\Http\Request;use App\Models\User;// Dentro de 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();});
Los guards definidos con Auth::viaRequest() no utilizan UserProvider, así que métodos del provider como retrieveById() no funcionan. En producción se recomienda un guard basado en clases mediante Auth::extend().
Si necesitas obtener los datos del usuario desde una fuente que no sea la base de datos (una API externa, LDAP, etc.), implementa la interfaz 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, ) {} /** * Obtiene el usuario por ID */ public function retrieveById(mixed $identifier): ?Authenticatable { return ($this->model)::find($identifier); } /** * Obtiene el usuario mediante el token "recordarme" * En guards sin sesión basta con devolver null */ public function retrieveByToken(mixed $identifier, string $token): ?Authenticatable { return null; } /** * Actualiza el token "recordarme" * En guards sin sesión no es necesario hacer nada */ public function updateRememberToken(Authenticatable $user, string $token): void {} /** * Obtiene el usuario a partir de credenciales * Se llama desde validate() y user() del guard */ public function retrieveByCredentials(array $credentials): ?Authenticatable { if (empty($credentials['api_token'])) { return null; } // Ejemplo: verifica el token contra una API externa y obtiene los datos del usuario $response = \Illuminate\Support\Facades\Http::withToken($credentials['api_token']) ->get("{$this->apiBaseUrl}/auth/me"); if (! $response->successful()) { return null; } $data = $response->json(); // Puedes vincularlo a un usuario local o crear el modelo dinámicamente return User::firstOrCreate( ['external_id' => $data['id']], ['name' => $data['name'], 'email' => $data['email']], ); } /** * Valida las credenciales del usuario recuperado * En autenticación por token basta con devolver true si retrieveByCredentials tuvo éxito */ public function validateCredentials(Authenticatable $user, array $credentials): bool { return true; } /** * Indica si es necesario rehashear la contraseña * En autenticación por token siempre puede ser false */ public function rehashPasswordIfRequired(Authenticatable $user, array $credentials, bool $force = false): void {}}
Autenticación contra una API externa con tokens JWT
Ejemplo de un guard personalizado que usa un servicio externo de autenticación JWT.
<?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; } // Verifica el JWT en un servicio externo $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'])); } /** * Verifica el JWT y obtiene su 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(); } /** * Devuelve el payload del JWT verificado */ public function payload(): ?array { return $this->payload; }}
Registro en AppServiceProvider:
Auth::extend('jwt', function (Application $app, string $name, array $config) { return new \App\Auth\JwtGuard( Auth::createUserProvider($config['provider'] ?? 'users'), $app->make('request'), );});
Puedes acceder también a métodos específicos del guard personalizado, como en Auth::guard('jwt')->payload(). Lo que devuelve Auth::guard() es la instancia del guard, así que también puedes invocar métodos que no estén en la interfaz.