Vai al contenuto principale

Struttura interna del sistema di autenticazione di Laravel

Facade Auth e AuthManager

La facade Auth è un proxy verso Illuminate\Auth\AuthManager. AuthManager gestisce più guard con il driver pattern e, in base alla configurazione di config/auth.php, crea e memorizza in cache la corretta istanza del guard.
// Comportamento interno di Auth::guard('web') (versione semplificata di AuthManager::guard())
public function guard($name = null)
{
    $name = $name ?: $this->getDefaultDriver();

    return $this->guards[$name] ?? ($this->guards[$name] = $this->resolve($name));
}
resolve() legge la chiave driver dall’array guards di config/auth.php e chiama la closure factory corrispondente. Anche i driver integrati session e token sono registrati con lo stesso meccanismo.

Differenza tra l’interfaccia Guard e StatefulGuard

Un guard di autenticazione Laravel deve implementare almeno Illuminate\Contracts\Auth\Guard. Se serve mantenere una sessione, implementa StatefulGuard.
interface Guard
{
    // verifica se esiste un utente autenticato
    public function check();

    // verifica se è un ospite (non autenticato)
    public function guest();

    // restituisce l'utente attualmente autenticato (null se non autenticato)
    public function user();

    // restituisce l'ID dell'utente attualmente autenticato
    public function id();

    // verifica se le credenziali indicate sono valide (senza effettuare il login)
    public function validate(array $credentials = []);

    // verifica se un utente è stato impostato (anche non loggato può essere iniettato con setUser)
    public function hasUser();

    // imposta manualmente l'utente autenticato
    public function setUser(Authenticatable $user);
}
StatefulGuard estende Guard e aggiunge i metodi necessari a mantenere lo stato di login tramite sessione o cookie.
interface StatefulGuard extends Guard
{
    // verifica le credenziali e fa il login (con flag remember)
    public function attempt(array $credentials = [], $remember = false);

    // autentica solo per una richiesta, senza salvare la sessione
    public function once(array $credentials = []);

    // effettua il login di un'istanza utente
    public function login(Authenticatable $user, $remember = false);

    // login tramite ID
    public function loginUsingId($id, $remember = false);

    // autentica per una sola richiesta usando l'ID
    public function onceUsingId($id);

    // verifica se il login è stato eseguito tramite il cookie "remember me"
    public function viaRemember();

    // effettua il logout
    public function logout();
}
Per un guard che non richiede sessione (autenticazione API, autenticazione con token personalizzato, ecc.) è sufficiente implementare Guard. Se ti serve una sessione — come il login amministratore — implementa StatefulGuard.

Implementazione di un guard personalizzato

Trait GuardHelpers

Poiché check(), guest(), id() e hasUser() di Guard hanno un’implementazione quasi identica, Laravel fornisce il trait Illuminate\Auth\GuardHelpers. Con questo trait devi implementare solo user() e validate().
// Implementazione predefinita fornita da GuardHelpers (estratto)
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;
    }
}

Esempio: guard di autenticazione tramite API token

Ispirandoci al design di TokenGuard, implementiamo un semplice guard di autenticazione con API token. Recupera il token dagli header o dai parametri della richiesta e risolve l’utente tramite UserProvider.
1

Creare la classe guard

Crea la classe guard nella directory 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;
    }

    /**
     * Restituisce l'utente attualmente autenticato
     */
    public function user(): ?\Illuminate\Contracts\Auth\Authenticatable
    {
        // se c'è già un utente in cache, restituiscilo
        if (! is_null($this->user)) {
            return $this->user;
        }

        $token = $this->getTokenForRequest();

        if (empty($token)) {
            return null;
        }

        // recupera l'utente tramite UserProvider
        $this->user = $this->provider->retrieveByCredentials([
            'api_token' => $token,
        ]);

        return $this->user;
    }

    /**
     * Verifica solo le credenziali (senza login)
     */
    public function validate(array $credentials = []): bool
    {
        if (empty($credentials['api_token'])) {
            return false;
        }

        return (bool) $this->provider->retrieveByCredentials($credentials);
    }

    /**
     * Recupera il token dalla richiesta
     *
     * Priorità: header Bearer → query string → corpo della richiesta
     */
    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;
    }

    /**
     * Sostituisce l'istanza Request
     */
    public function setRequest(Request $request): static
    {
        $this->request = $request;
        return $this;
    }
}
2

Registrare il guard nel service provider

Nel metodo boot() di AppServiceProvider registra il guard con Auth::extend().
<?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() risolve il provider da config
            $provider = Auth::createUserProvider($config['provider'] ?? 'users');

            return new ApiTokenGuard($provider, $app->make('request'));
        });
    }
}
Auth::createUserProvider() legge la configurazione providers di config/auth.php e restituisce l’istanza UserProvider corrispondente. Finché non crei un provider personalizzato, con questa chiamata usi l’EloquentUserProvider standard.
3

Configurare il guard in config/auth.php

Aggiungi il nuovo guard a config/auth.php.
'guards' => [
    'web' => [
        'driver'   => 'session',
        'provider' => 'users',
    ],

    // guard personalizzato aggiunto
    'api' => [
        'driver'   => 'api-token', // deve coincidere con il primo argomento di Auth::extend()
        'provider' => 'users',
    ],
],
4

Applicare il guard alle rotte

Nel middleware auth indichi il nome del guard.
// 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']);
});
Per usare un guard specifico in un controller o nel codice, chiami Auth::guard('api') o auth('api').
$user = Auth::guard('api')->user();

Guard rapido con closure

Con Auth::viaRequest() puoi definire un guard semplice usando solo una closure, senza creare una classe. Adatto a prototipi o ad autenticazioni molto semplici.
use Illuminate\Http\Request;
use App\Models\User;

// dentro 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();
});
Configurazione in config/auth.php:
'guards' => [
    'api' => [
        'driver' => 'custom-token',
    ],
],
Un guard definito con Auth::viaRequest() non usa un UserProvider, quindi metodi del provider come retrieveById() non funzionano. In produzione consigliamo un guard class-based con Auth::extend().

Implementare un UserProvider personalizzato

Se recuperi le informazioni utente da una sorgente diversa dal DB (API esterne, LDAP, ecc.) implementa l’interfaccia 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,
    ) {}

    /**
     * Recupera un utente per ID
     */
    public function retrieveById(mixed $identifier): ?Authenticatable
    {
        return ($this->model)::find($identifier);
    }

    /**
     * Recupera un utente con il token "remember me"
     * Nei guard senza sessione può restituire null
     */
    public function retrieveByToken(mixed $identifier, string $token): ?Authenticatable
    {
        return null;
    }

    /**
     * Aggiorna il token "remember me"
     * Nei guard senza sessione può non fare nulla
     */
    public function updateRememberToken(Authenticatable $user, string $token): void {}

    /**
     * Recupera un utente tramite credenziali
     * Chiamato da validate() e user() del guard
     */
    public function retrieveByCredentials(array $credentials): ?Authenticatable
    {
        if (empty($credentials['api_token'])) {
            return null;
        }

        // esempio: valida il token su un'API esterna e recupera i dati utente
        $response = \Illuminate\Support\Facades\Http::withToken($credentials['api_token'])
            ->get("{$this->apiBaseUrl}/auth/me");

        if (! $response->successful()) {
            return null;
        }

        $data = $response->json();

        // collega all'utente locale nel DB o genera dinamicamente il modello
        return User::firstOrCreate(
            ['external_id' => $data['id']],
            ['name' => $data['name'], 'email' => $data['email']],
        );
    }

    /**
     * Verifica le credenziali dell'utente recuperato
     * Nell'autenticazione a token è sufficiente restituire true se retrieveByCredentials ha avuto successo
     */
    public function validateCredentials(Authenticatable $user, array $credentials): bool
    {
        return true;
    }

    /**
     * Verifica se è necessario ricalcolare l'hash della password
     * Nell'autenticazione a token restituisci sempre false (o non fare nulla)
     */
    public function rehashPasswordIfRequired(Authenticatable $user, array $credentials, bool $force = false): void {}
}

Registrazione di un UserProvider personalizzato

// 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,
    );
});
Aggiungi la sezione providers in config/auth.php:
'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model'  => App\Models\User::class,
    ],

    // provider personalizzato
    'api-users' => [
        'driver' => 'api-user',
        'model'  => App\Models\User::class,
    ],
],
Combina guard e provider:
'guards' => [
    'api' => [
        'driver'   => 'api-token',
        'provider' => 'api-users', // indica il provider personalizzato
    ],
],

Casi d’uso pratici

Multi-authentication (guard separati per admin e utente)

1

Creare il modello Admin

Prepara un modello Eloquent per gli amministratori. Estendendo Authenticatable si integra con il 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

Configurare config/auth.php

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

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

Impostare rotte e middleware

// routes/web.php

// rotte per gli utenti (guard web di default)
Route::middleware('auth')->group(function () {
    Route::get('/dashboard', [DashboardController::class, 'index']);
});

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

Scrivere il login indicando il 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',
        ]);

        // fa attempt indicando esplicitamente il guard admin
        if (Auth::guard('admin')->attempt($credentials)) {
            $request->session()->regenerate();
            return redirect()->intended('/admin/dashboard');
        }

        return back()->withErrors(['email' => 'Credenziali non valide.']);
    }

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

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

Autenticazione API esterna con JWT

Esempio di guard personalizzato che usa un servizio esterno di autenticazione 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 il JWT tramite servizio esterno
        $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 il JWT e recupera il 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();
    }

    /**
     * Restituisce il payload JWT verificato
     */
    public function payload(): ?array
    {
        return $this->payload;
    }
}
Registrazione in AppServiceProvider:
Auth::extend('jwt', function (Application $app, string $name, array $config) {
    return new \App\Auth\JwtGuard(
        Auth::createUserProvider($config['provider'] ?? 'users'),
        $app->make('request'),
    );
});
Puoi accedere anche a metodi specifici del guard personalizzato, ad esempio Auth::guard('jwt')->payload(). Auth::guard() restituisce l’istanza stessa del guard, quindi puoi chiamare anche metodi non presenti nell’interfaccia.

Test

Nei test unitari di un guard personalizzato, mocki UserProvider e verifichi il comportamento 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([]));
    }
}
Nei feature test con ActingAs puoi autenticare un utente su un guard specifico.
// autentica su un guard specifico durante il test
$this->actingAs($user, 'api')
    ->getJson('/api/user')
    ->assertOk();

Pagine correlate

Autenticazione (introduzione)

Rivedi gli starter kit e il flusso di autenticazione standard.

Service container

Comprendi il meccanismo del service container usato per registrare i guard.
Ultima modifica il 13 luglio 2026