Understand Laravel’s authentication internals and implement custom guards using the Guard and StatefulGuard interfaces, from API token authentication to multi-guard applications.
The Auth facade proxies Illuminate\Auth\AuthManager. The manager uses a driver pattern to manage multiple guards and creates the appropriate guard instance based on config/auth.php.
resolve() reads the driver key from the guards array in config/auth.php and invokes the matching factory closure. The built-in session and token drivers use the same mechanism.
Any authentication guard must implement at minimum Illuminate\Contracts\Auth\Guard. If the guard needs to maintain session state, implement StatefulGuard instead.
Guard interface (Illuminate\Contracts\Auth\Guard)
interface Guard{ // Check whether an authenticated user exists public function check(); // Check whether the current user is a guest (unauthenticated) public function guest(); // Return the currently authenticated user, or null public function user(); // Return the ID of the currently authenticated user public function id(); // Validate credentials without logging in public function validate(array $credentials = []); // Check whether a user has been set on the guard public function hasUser(); // Manually set the authenticated user public function setUser(Authenticatable $user);}
StatefulGuard extends Guard and adds methods for session and cookie-based login state.
interface StatefulGuard extends Guard{ // Validate credentials and log the user in public function attempt(array $credentials = [], $remember = false); // Authenticate for a single request without storing state public function once(array $credentials = []); // Log in a user instance directly public function login(Authenticatable $user, $remember = false); // Log in using a user ID public function loginUsingId($id, $remember = false); // Authenticate using a user ID for a single request public function onceUsingId($id); // Determine whether the user logged in via the "remember me" cookie public function viaRemember(); // Log the user out public function logout();}
Guards for API or token-based authentication typically only need to implement Guard because there is no session state to maintain. Guards that need session-based login (e.g. an admin panel) should implement StatefulGuard.
The implementations of check(), guest(), id(), and hasUser() are nearly identical across all guards. Laravel provides Illuminate\Auth\GuardHelpers to handle them automatically, so you only need to implement user() and validate().
// Default implementations provided by GuardHelpers (excerpt)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; }}
The example below follows the design of Laravel’s built-in TokenGuard. It reads a token from the Authorization header or a query parameter and resolves the user through the configured UserProvider.
1
Create the guard class
Place the guard class in 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; } /** * Return the currently authenticated user. */ public function user(): ?\Illuminate\Contracts\Auth\Authenticatable { // Return the cached user if already resolved if (! is_null($this->user)) { return $this->user; } $token = $this->getTokenForRequest(); if (empty($token)) { return null; } // Resolve the user via the UserProvider $this->user = $this->provider->retrieveByCredentials([ 'api_token' => $token, ]); return $this->user; } /** * Validate credentials without logging in. */ public function validate(array $credentials = []): bool { if (empty($credentials['api_token'])) { return false; } return (bool) $this->provider->retrieveByCredentials($credentials); } /** * Extract the token from the request. * * Priority: Bearer header → query parameter → request body */ 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; } /** * Replace the current request instance. */ public function setRequest(Request $request): static { $this->request = $request; return $this; }}
2
Register the guard in a service provider
Register the guard in the boot() method of AppServiceProvider using 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) { $provider = Auth::createUserProvider($config['provider'] ?? 'users'); return new ApiTokenGuard($provider, $app->make('request')); }); }}
Auth::createUserProvider() reads the providers section of config/auth.php and returns the matching UserProvider instance. Unless you need a custom provider, this returns the standard EloquentUserProvider.
3
Configure the guard in config/auth.php
Add the new guard to the guards array.
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], // Your custom guard 'api' => [ 'driver' => 'api-token', // must match the name passed to Auth::extend() 'provider' => 'users', ],],
Auth::viaRequest() lets you define a simple guard using only a closure — no class required. This is suitable for prototypes or very straightforward authentication needs.
use Illuminate\Http\Request;use App\Models\User;// Inside 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();});
Guards created with Auth::viaRequest() bypass the UserProvider entirely. Methods like retrieveById() will not be called. For production use, prefer a class-based guard registered with Auth::extend().
When user data comes from a source other than the database — an external API, LDAP, etc. — implement 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, ) {} /** * Retrieve a user by their ID. */ public function retrieveById(mixed $identifier): ?Authenticatable { return ($this->model)::find($identifier); } /** * Retrieve a user by their "remember me" token. * Return null for stateless/token guards. */ public function retrieveByToken(mixed $identifier, string $token): ?Authenticatable { return null; } /** * Update the "remember me" token. * No-op for stateless guards. */ public function updateRememberToken(Authenticatable $user, string $token): void {} /** * Retrieve a user by the given credentials. * Called by the guard's user() and validate() methods. */ public function retrieveByCredentials(array $credentials): ?Authenticatable { if (empty($credentials['api_token'])) { return null; } // Validate the token against an external API $response = \Illuminate\Support\Facades\Http::withToken($credentials['api_token']) ->get("{$this->apiBaseUrl}/auth/me"); if (! $response->successful()) { return null; } $data = $response->json(); // Sync with the local database or create a stub model return User::firstOrCreate( ['external_id' => $data['id']], ['name' => $data['name'], 'email' => $data['email']], ); } /** * Validate the user's credentials. * For token guards, returning true is sufficient after retrieveByCredentials succeeds. */ public function validateCredentials(Authenticatable $user, array $credentials): bool { return true; } /** * Rehash the user's password if required. * No-op for token guards. */ public function rehashPasswordIfRequired(Authenticatable $user, array $credentials, bool $force = false): void {}}