In Laravel, how do Auth::guard() and Auth::shouldUse() decide which guard Auth::user() and $request->user() consult?
answer
- no name means auth.defaults.guard
- facade calls proxy to the default guard
- guard instances cached per name
- shouldUse rewrites the default at runtime
- auth:admin calls shouldUse for you
basics
~20 sWithout a name, Auth::user() and $request->user() use the guard in auth.defaults.guard, normally web. Auth::guard('admin') asks a named guard explicitly; Auth::shouldUse('admin') changes the default for the rest of the request, which the auth:admin middleware does itself.
solid answer
~40 sThe `Auth` facade resolves `AuthManager`, and any method it does not define itself, such as `user()` or `check()`, is forwarded to the **default guard**, whose name comes from `auth.defaults.guard` (`web` in the skeleton). `$request->user($guard = null)` goes through the manager's user resolver, which does the same. `Auth::guard('admin')` returns the named guard, building it once and caching it for the request. `Auth::shouldUse('admin')` writes `admin` into `auth.defaults.guard` at runtime and resets the user resolver, so every later unnamed call, including `$request->user()` and the authorization layer's user lookup, sees the admin guard. The `auth:admin` middleware calls `shouldUse` on the first guard that has a user, which is why code inside those routes can stay unnamed. In Laravel 13, both methods also accept an enum case as the guard name (added in 13.5).
code
php · 18 lines<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
// Public page: no auth middleware, default guard stays 'web'.
Route::get('/vehicles', function (Request $request) {
$customer = Auth::user(); // web guard
$staff = Auth::guard('admin')->user(); // admin guard, explicit
return view('vehicles.index', compact('customer', 'staff'));
});
// Back office: auth:admin calls Auth::shouldUse('admin').
Route::middleware('auth:admin')->get('/back-office', function (Request $request) {
return $request->user()->name; // the staff member
});go deeper
Remember that Auth::user() without a name reads the default guard, web in a fresh app, and Auth::guard('admin') asks a specific one.
Explain the facade forwarding to the default guard, the per-request guard cache, and how shouldUse rewrites auth.defaults.guard and resets the user resolver.
Diagnose a null user in a multi-guard app by tracing which middleware ran, and prefer explicit guard names in shared layouts and services over changing global defaults.
Decide team conventions for guard naming in shared code, since implicit defaults make multi-guard behaviour depend on route middleware nobody reads at the call site.
## The default guard Every application has one **default guard**, named by `auth.defaults.guard` in `config/auth.php`. The Laravel 13 skeleton sets it to `env('AUTH_GUARD', 'web')`. Anything that asks for "the current user" without naming a guard ends up there: - `Auth::user()`, `Auth::check()`, `Auth::id()` and other guard methods called on the facade. The facade resolves `Illuminate\Auth\AuthManager`, and its `__call` forwards unknown methods to `$this->guard()`, the default guard. - The `auth()` helper without arguments, which returns the same manager. - `$request->user()` with no argument. The request calls the user resolver the manager registered, which is `fn ($guard = null) => $this->guard($guard)->user()`. So in a multi-guard application, **unnamed calls are only as right as the default guard is for that request**. ## `Auth::guard($name)`: ask one guard explicitly `Auth::guard('admin')` returns the guard configured under `auth.guards.admin`. The manager builds it on first use and keeps it in an internal array keyed by name, so every later call in the same request gets the same instance (and the same cached user). With no argument, `guard()` returns the default guard. Use the explicit form when code must be correct regardless of which routes it runs under, for example a shared layout that shows "signed in as staff" only when `Auth::guard('admin')->check()` is true. `$request->user('admin')` is the request-side equivalent. ## `Auth::shouldUse($name)`: change the default for this request `shouldUse` does two things, both visible in `AuthManager`: 1. It calls `setDefaultDriver($name)`, which writes the name into the `auth.defaults.guard` **config value at runtime**. 2. It replaces the user resolver with a fresh closure, so `$request->user()` and anything else that reads the resolver now lands on the new default. The authorization layer asks the same resolver for the current user, so gates and policies also start seeing the admin user. Nothing is persisted: the next request starts from the configured default again (under PHP-FPM every request boots a fresh application). ## Where `shouldUse` gets called for you The `auth` middleware (`Illuminate\Auth\Middleware\Authenticate`) accepts guard names as parameters, as in `auth:admin` or `auth:admin,web`. For each listed guard it checks whether a user is present and, on the **first match, calls `$this->auth->shouldUse($guard)`**. That is the reason a controller behind `auth:admin` can call `Auth::user()` and get the staff member rather than `null`. | Call | Guard it consults | |---|---| | `Auth::user()` on a route with no auth middleware | `auth.defaults.guard` from config | | `Auth::user()` behind `auth:admin` | `admin`, because the middleware called `shouldUse` | | `Auth::guard('admin')->user()` anywhere | `admin`, always | | `$request->user('web')` anywhere | `web`, always | ## The classic bug A car-dealership app has a `web` guard for customers and an `admin` guard for sales staff. A staff member signs in through the admin guard and opens a public page, such as the vehicle listing, that has no auth middleware. The Blade layout calls `Auth::user()` to show a "back to the back office" link and gets `null`: the default guard is still `web`, and the staff member never signed in through it. The fix is to ask explicitly, `Auth::guard('admin')->check()`, not to change `AUTH_GUARD`, which would flip every customer-facing call in the application. ## Laravel 13 detail Since 13.5, `guard()` and `shouldUse()` run the name through `enum_value()`, so a backed enum case such as `Guard::Admin` works wherever a string name did. The config keys themselves remain strings. ## Rules of thumb for multi-guard code - **Controllers behind `auth:<guard>`** may use unnamed calls, because the middleware has already made that guard the default. - **Shared Blade layouts, view composers and services** should name the guard, because they run under routes with different middleware, or none. - **Routes without any auth middleware** always start from `auth.defaults.guard`; nothing remembers which guard the visitor used last time. - **Calling `shouldUse` by hand** is rare: it suits a custom middleware that picks a guard by subdomain or header before the rest of the stack runs. Put it early, because anything that already read the user earlier in the request keeps the answer it got. - **Keep `AUTH_GUARD` for the majority audience**, usually customers on `web`, and treat every other guard as something code opts into by name.
- Why not just set AUTH_GUARD=admin to fix Auth::user() returning null for staff?`AUTH_GUARD` sets the default for the whole application, so every unnamed call on customer pages, and the default for any `auth` middleware used without a parameter, would then consult the admin guard. Customers would appear signed out. Keep the default on the guard most routes use, name the other guard explicitly, and let `auth:admin` switch it where needed.
- With auth:admin,web on a route, which guard becomes the default?The middleware checks the guards in the order listed and calls `shouldUse` on the first one that has an authenticated user. A signed-in staff member makes `admin` the default; a customer with no admin session makes `web` the default. If neither has a user, it throws `AuthenticationException` carrying both guard names.
saying these in an interview costs you the question
- Auth::user() checks every configured guard until it finds a user.
- shouldUse permanently changes the default guard for all later requests.
- Auth::guard('admin') creates a new guard instance on every call.
- Setting AUTH_GUARD to admin only affects the admin routes.
- $request->user() always reads the web guard regardless of middleware.