In Laravel, when would you register a custom guard with Auth::viaRequest rather than Auth::extend, and where does Auth::provider fit?
answer
- closure versus a full Guard class
- viaRequest builds a RequestGuard
- extend callback gets app, name, config
- Auth::provider returns a UserProvider
- register in AppServiceProvider::boot
basics
~20 sAuth::viaRequest wraps a closure that takes the request and returns a user or null in a stateless RequestGuard; Auth::extend registers a factory for your own Guard class. Auth::provider is different: it adds a user-storage driver, not a new way to authenticate.
solid answer
~40 sAll three are called in a service provider's `boot` method and register a **driver name** you then reference in `config/auth.php`. `Auth::viaRequest('dealer-key', fn (Request $request) => ...)` is the shortcut: Laravel wraps the closure in a `RequestGuard`, calls it once per request and caches the result; it implements only `Guard`, so there is no `attempt()` or `login()`. `Auth::extend('jwt', fn ($app, $name, $config) => new JwtGuard(...))` registers a factory for a class you write against `Illuminate\Contracts\Auth\Guard` (or `StatefulGuard`), and receives the guard's config, so it can use `Auth::createUserProvider($config['provider'])`. `Auth::provider('ldap', fn ($app, $config) => ...)` returns a `UserProvider` for users stored outside a SQL table. Use `viaRequest` for a header or key check, `extend` when you need more methods, state or configuration, and `provider` when storage rather than transport is custom.
code
php · 22 lines<?php
namespace App\Providers;
use App\Models\PartnerLot;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Auth::viaRequest('dealer-key', function (Request $request) {
$key = (string) $request->header('X-Dealer-Key');
return $key === ''
? null
: PartnerLot::where('api_key_hash', hash('sha256', $key))->first();
});
}
}go deeper
Recall that custom auth drivers are registered in a service provider's boot method and then referenced by name in config/auth.php.
Explain that viaRequest wraps a closure in a stateless RequestGuard, extend takes a factory for a full Guard class, and provider adds a UserProvider driver.
Pick the right extension point for a partner-key, JWT or directory-backed login, and know when installing Sanctum beats writing a guard at all.
Weigh owning custom authentication code against adopting a maintained package, counting the review and upgrade burden every custom guard carries.
## Three extension points Laravel's auth manager lets you add drivers of two kinds: **guard drivers** (how a request is authenticated) and **provider drivers** (where users are loaded from). Each registration gives a **driver name** that `config/auth.php` then references. Call them from `boot()` in `AppServiceProvider` or another service provider. | API | Registers | Callback receives | Must return | |---|---|---|---| | `Auth::viaRequest($driver, $callback)` | a guard driver | the request (and a provider) | a user or `null` | | `Auth::extend($driver, $callback)` | a guard driver | `$app`, the guard name, its config array | an `Illuminate\Contracts\Auth\Guard` | | `Auth::provider($driver, $callback)` | a provider driver | `$app`, the provider's config array | an `Illuminate\Contracts\Auth\UserProvider` | ## `Auth::viaRequest`: a closure guard `viaRequest` is a shortcut built on `extend`. Laravel wraps your closure in `Illuminate\Auth\RequestGuard`, which: - calls the closure the first time `user()` is asked for in a request and caches the answer; - implements `Guard` only, so there is no `attempt()`, `login()` or `logout()`: the request proves itself each time; - supports `check()`, `guest()` and `id()` through the shared guard helpers, plus its own `validate()`. A dealership that lets partner lots push inventory with a static key header fits it well: the closure looks up the key and returns the matching partner account. Reference the driver in a guard, `'partners' => ['driver' => 'dealer-key']`, then protect routes with `auth:partners`. One source detail: the closure's second argument is the provider named by `auth.defaults.provider`, which the Laravel 13 skeleton does not define, so it is `null` unless you add that key. The guard's own `provider` key is not consulted. The documented pattern queries the model directly, which avoids the issue. ## `Auth::extend`: a guard class Use `extend` when a closure is not enough: 1. You need guard-specific configuration from `config/auth.php`, because the callback receives the guard's config array. 2. You want the guard's configured provider, via `Auth::createUserProvider($config['provider'])`. 3. You need a stateful guard with `login()` and `logout()`, which means implementing `StatefulGuard`. 4. You want a class you can unit-test and reuse, such as a guard that verifies a signed JWT. The callback is bound to the auth manager and cached per guard name for the request, like the built-in guards. Sanctum and Passport register their `sanctum` and `passport` drivers exactly this way. ## `Auth::provider`: a storage driver `Auth::provider('ldap', fn ($app, array $config) => new LdapUserProvider(...))` answers a different question: users are not in a table an Eloquent model or the query builder can read. The returned class implements `UserProvider` (`retrieveById`, `retrieveByToken`, `updateRememberToken`, `retrieveByCredentials`, `validateCredentials`, `rehashPasswordIfRequired`). You then declare `'providers' => ['staff' => ['driver' => 'ldap']]` and point an ordinary `session` guard at it: the session guard stays built-in, only the storage changes. ## Choosing - Custom **transport**, simple lookup: `viaRequest`. - Custom **transport**, needs config, state or several methods: `extend`. - Custom **storage**, standard transport: `provider` plus the `session` driver. - Standard API tokens: install Sanctum instead of writing any of these. An unregistered driver name in `guards` fails with "Auth driver [x] for guard [y] is not defined.", and in `providers` with "Authentication user provider [x] is not defined." ## Pitfalls - **Registering too late.** The guard is resolved the first time something asks for it; register drivers in a provider's `boot` method so they exist before any middleware runs. - **Comparing secrets carelessly.** A `viaRequest` closure that looks up a raw key with `where('api_key', $key)` stores the key in plain text; hashing the key and looking up the hash, as in the example, keeps a database leak from handing out working keys. - **Returning the wrong thing.** The closure must return an `Authenticatable` or `null`. The guard helpers test `check()` with `! is_null($this->user())`, so a closure that returns `false` for a bad key makes `check()` true and lets the `auth` middleware wave the request through. - **Forgetting statelessness.** Because a `RequestGuard` keeps nothing between requests, every request pays for the lookup; cache inside the closure only if the lookup is genuinely expensive.
- Can you call Auth::guard('partners')->login($lot) on a viaRequest guard?No. `viaRequest` produces a `RequestGuard`, which implements only `Illuminate\Contracts\Auth\Guard`. It has `setUser()` for the current request but no `login()`, `attempt()` or `logout()`, because it keeps no state between requests.
- Why pass $config['provider'] to Auth::createUserProvider inside an extend callback?It builds whichever provider the guard's config names, so the same guard class can serve customers with `users` and staff with `staff` just by changing `config/auth.php`, instead of hard-coding a model in the guard.
saying these in an interview costs you the question
- viaRequest guards can sign a user in and keep them in the session.
- Auth::provider is how you add a new way of reading tokens from requests.
- Custom guards must be registered in bootstrap/app.php withMiddleware.
- A viaRequest guard uses the provider key from its own guard config.
- Writing a custom token guard is the recommended way to add API tokens.