Auth middleware is Laravel’s gatekeeper for protected routes. In Laravel 12, the auth
middleware is an alias to Illuminate\Auth\Middleware\Authenticate
and it’s configured in bootstrap/app.php
(not in app/Http/Kernel.php
like older versions). Below you’ll find: (1) a short history of what moved, (2) what the middleware actually does under the hood, and (3) copy-paste snippets with explanations so your team understands why each piece is used.
1) A Short History: What Changed by Laravel 12?
Before Laravel 11, you registered global/group/alias middleware in app/Http/Kernel.php
. Starting in Laravel 11 and continuing in Laravel 12, middleware configuration moved to bootstrap/app.php
using the ->withMiddleware()
callback. The default auth
alias points to the framework’s Illuminate\Auth\Middleware\Authenticate
. If you need custom behaviour (e.g., redirect URL), create your own class and re-alias it in bootstrap/app.php
.
// bootstrap/app.php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Middleware;
use App\Http\Middleware\EnsureTeamIsActive;
return Application::configure(basePath: dirname(__DIR__))
->withRouting( /* routes/web.php, routes/api.php, etc. */ )
->withMiddleware(function (Middleware $middleware) {
// Define your own aliases (the 'auth' alias already exists by default)
$middleware->alias([
'team.active' => EnsureTeamIsActive::class,
]);
// You can also prepend/append to groups like 'web' or 'api':
// $middleware->appendToGroup('web', \App\Http\Middleware\LogWebTiming::class);
// $middleware->prepend(\App\Http\Middleware\TrustHosts::class);
})
->create();
Code language: PHP (php)
Why this is used: This shows the new, Laravel-12-style place where middleware is configured. You’ll come here when adding new middleware aliases or adjusting how groups like web
/api
behave. The auth
alias is already available, so you usually don’t have to touch it unless you want to override.
2) What the Auth Middleware Actually Does (Functions & Flow)
The Authenticate
middleware’s key methods are:
handle($request, Closure $next, ...$guards)
— Entry point. Runs before your controller and decides whether the request can proceed.authenticate($request, array $guards)
— Tries each guard (e.g.,web
,sanctum
). ThrowsAuthenticationException
if none authenticates.redirectTo(Request $request)
— For browser requests that expect HTML, returns the login URL when unauthenticated. For API/JSON requests, the middleware typically returns a 401 JSON response instead of redirecting.
Why this matters: Understanding these functions explains why web visitors get redirected to login while API requests get a 401. It’s the same middleware, but behaviour adapts to the request type and guard.
3) Core Usage: Protecting Routes (Web)
Apply the built-in auth
alias to routes or groups to block unauthenticated access:
use Illuminate\Support\Facades\Route;
Route::get('/dashboard', fn () => view('dashboard'))
->middleware('auth'); // only logged-in users
Route::middleware('auth')->group(function () {
Route::get('/settings', fn () => view('settings'));
Route::get('/billing', fn () => view('billing'));
});
Code language: PHP (php)
Why this is used: This is the most common pattern for protecting back-office pages. If a visitor isn’t logged in (no valid web
session), the middleware triggers a redirect (via redirectTo()
) to your login route.
4) APIs: Specify a Guard (Sanctum Example)
For token-based APIs, specify the sanctum
guard:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
// Returns the authenticated API user (requires Sanctum token)
Route::middleware('auth:sanctum')->get('/api/user', function (Request $request) {
return $request->user();
});
Code language: PHP (php)
Why this is used: Adding :sanctum
tells Authenticate
which guard to check. Unauthenticated API calls get a 401 JSON (not a browser redirect), which is the expected behaviour for clients.
5) Multiple Guards & Order
You can list several guards; Laravel tries them in order:
// Try 'admin' guard first, then fall back to 'web'
Route::get('/admin', [\App\Http\Controllers\AdminController::class, 'index'])
->middleware('auth:admin,web');
Code language: PHP (php)
Why this is used: Useful when admins have a separate guard or provider but you still want to let regular authenticated users in as a fallback. The first successful guard wins.
6) Overriding the Login Redirect (Customize redirectTo()
)
If you want to control where unauthenticated web users are sent, extend the framework middleware and re-alias auth
to your class:
// app/Http/Middleware/Authenticate.php
namespace App\Http\Middleware;
use Illuminate\Http\Request;
class Authenticate extends \Illuminate\Auth\Middleware\Authenticate
{
/**
* Get the path the user should be redirected to when not authenticated.
*/
protected function redirectTo(Request $request): ?string
{
// Only redirect for HTML visits; APIs should keep 401 JSON
return $request->expectsHtml()
? route('login') // or 'auth.signin' etc.
: null;
}
}
Code language: PHP (php)
Why this is used: Overriding redirectTo()
gives you total control of the browser redirect target without affecting API behaviour (which should remain 401 JSON).
// bootstrap/app.php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Middleware;
use App\Http\Middleware\Authenticate; // our override
return Application::configure(basePath: dirname(__DIR__))
->withRouting( /* ... */ )
->withMiddleware(function (Middleware $middleware) {
// Re-map the 'auth' alias to use our local Authenticate override
$middleware->alias([
'auth' => Authenticate::class,
]);
})
->create();
Code language: PHP (php)
Why this is used: The alias mapping above makes ->middleware('auth')
use your local class everywhere, ensuring consistent redirects across the app.
7) Route Groups for Dashboards
Group all authenticated pages together for cleaner files and consistent protection:
Route::middleware(['auth'])->group(function () {
Route::view('/app', 'app.index');
Route::view('/profile', 'profile.show');
Route::get('/orders', [\App\Http\Controllers\OrderController::class, 'index']);
});
Code language: CSS (css)
Why this is used: Keeps your route files tidy and guarantees that every route in the group requires a session. Easier to reason about and audit.
8) Testing Authenticated & Guest Flows
Write tests to confirm redirects for guests and access for authenticated users:
// tests/Feature/DashboardTest.php
use Tests\TestCase;
it('redirects guests to login', function () {
$this->get('/dashboard')->assertRedirect(route('login'));
});
it('allows authenticated users', function () {
$user = \App\Models\User::factory()->create();
$this->actingAs($user)
->get('/dashboard')
->assertOk();
});
Code language: PHP (php)
Why this is used: These tests lock in expected behaviour: guests are bounced to login, logged-in users get 200 OK. The actingAs()
helper simulates a valid session for the web
guard.
9) Troubleshooting Cheatsheet
- HTML redirect instead of JSON for API: Ensure the request has
Accept: application/json
or that the route usesauth:sanctum
. APIs should return 401 JSON, not redirect. - Wrong redirect URL: Override
redirectTo()
in your local middleware and re-aliasauth
inbootstrap/app.php
. - Alias seems ignored: Clear caches after changes.
php artisan route:clear
php artisan config:clear
php artisan optimize:clear
Code language: CSS (css)
Why this is used: Laravel caches can mask recent changes to routing/middleware. Clearing them ensures your new aliases and redirects take effect immediately.
Key takeaway: In Laravel 12 you still use ->middleware('auth')
exactly as before. The big change is where middleware is configured (now bootstrap/app.php
). When you need custom redirects or guard behaviour, extend the middleware and re-alias it once — the rest of your app benefits automatically.
0 Comments