Implementare un guard di autenticazione personalizzato
Comprendi la struttura interna del sistema di autenticazione di Laravel e crea un guard personalizzato implementando le interfacce Guard e StatefulGuard, spiegato a livello di codice sorgente del framework.
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.
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.
<?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; } /** * 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().
<?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() 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', ],],
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();});
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().
Se recuperi le informazioni utente da una sorgente diversa dal DB (API esterne, LDAP, ecc.) implementa l’interfaccia 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, ) {} /** * 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 {}}
Esempio di guard personalizzato che usa un servizio esterno di autenticazione 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 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.