Saltar al contenido principal

Estructura interna del sistema de autenticación de Laravel

La facade Auth y AuthManager

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

Implementar un guard personalizado

El trait GuardHelpers

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.
<?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;
    }

    /**
     * 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.
<?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() 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',
    ],
],
4

Aplica el guard a las rutas

Indica el nombre del guard en el middleware auth.
// 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']);
});
Para usar un guard concreto en un controlador o en el código, llama a Auth::guard('api') o auth('api').
$user = Auth::guard('api')->user();

Guard sencillo mediante closure

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();
});
Configuración en config/auth.php:
'guards' => [
    'api' => [
        'driver' => 'custom-token',
    ],
],
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().

Implementar un UserProvider personalizado

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.
<?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,
    ) {}

    /**
     * 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 {}
}

Registro del UserProvider personalizado

// 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,
    );
});
Añádelo en la sección providers de config/auth.php:
'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model'  => App\Models\User::class,
    ],

    // Provider personalizado
    'api-users' => [
        'driver' => 'api-user',
        'model'  => App\Models\User::class,
    ],
],
Combina el guard con el provider:
'guards' => [
    'api' => [
        'driver'   => 'api-token',
        'provider' => 'api-users', // especifica el provider personalizado
    ],
],

Casos de uso prácticos

Autenticación múltiple (guards diferentes para administradores y usuarios normales)

1

Crea el modelo de administrador

Prepara un modelo Eloquent para el administrador. Con la herencia de Authenticatable, se integra en el sistema Auth.
php artisan make:model Admin -m
<?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'];
}
2

Configura config/auth.php

'guards' => [
    'web' => [
        'driver'   => 'session',
        'provider' => 'users',
    ],
    'admin' => [
        'driver'   => 'session',
        'provider' => 'admins', // provider de administradores
    ],
],

'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model'  => App\Models\User::class,
    ],
    'admins' => [
        'driver' => 'eloquent',
        'model'  => App\Models\Admin::class, // modelo de administrador
    ],
],
3

Configura rutas y middleware

// routes/web.php

// Rutas para usuarios normales (guard web por defecto)
Route::middleware('auth')->group(function () {
    Route::get('/dashboard', [DashboardController::class, 'index']);
});

// Rutas para administradores (guard admin)
Route::prefix('admin')->middleware('auth:admin')->group(function () {
    Route::get('/dashboard', [AdminDashboardController::class, 'index']);
});
4

Escribe el proceso de login indicando el guard

<?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',
        ]);

        // Llama a attempt indicando explícitamente el guard admin
        if (Auth::guard('admin')->attempt($credentials)) {
            $request->session()->regenerate();
            return redirect()->intended('/admin/dashboard');
        }

        return back()->withErrors(['email' => 'Las credenciales no son correctas.']);
    }

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

        return redirect('/admin/login');
    }
}

Autenticación contra una API externa con tokens JWT

Ejemplo de un guard personalizado que usa un servicio externo de autenticación JWT.
<?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;
        }

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

Pruebas

En los tests unitarios de un guard personalizado, mockea el UserProvider para comprobar el comportamiento del guard.
<?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([]));
    }
}
En los tests de funcionalidad puedes usar ActingAs para autenticar un usuario en un guard concreto.
// Autenticar un usuario en un guard específico para el test
$this->actingAs($user, 'api')
    ->getJson('/api/user')
    ->assertOk();

Páginas relacionadas

Autenticación (introducción)

Revisa los starter kits y el flujo de autenticación estándar.

Contenedor de servicios

Entiende el funcionamiento del contenedor de servicios que se usa al registrar guards.
Última modificación el 13 de julio de 2026