Authentication and authorization
Starter-kit scaffolding, guards and providers, Gates and Policies, email verification, password reset and where the checks belong.
Guards, providers and scaffolding
// config/auth.php essentials
'guards' => [
'web' => ['driver' => 'session', 'provider' => 'users'],
'api' => ['driver' => 'sanctum', 'provider' => 'users'],
],
'providers' => [
'users' => ['driver' => 'eloquent', 'model' => App\Models\User::class],
],
// routes/web.php from Breeze
Route::middleware('auth')->group(function () {
Route::get('/dashboard', DashboardController::class)->name('dashboard');
});
Route::middleware('guest')->group(function () {
Route::get('/login', [AuthenticatedSessionController::class, 'create']);
});
// customising the credentials used at login
if (! Auth::attempt(['email' => $email, 'password' => $password, 'active' => true], $remember)) {
return back()->withErrors(['email' => 'Those credentials do not match.']);
}
Auth::guard('web')->logout();
request()->session()->invalidate();
request()->session()->regenerateToken();| Concern | Where it lives | Note |
|---|---|---|
| Who the user is | Guard + provider | Session guard for browsers, token guard for APIs |
| Is the session still valid | AuthenticateSession middleware | Invalidates other sessions after a password change |
| Email ownership | verified middleware | Plus MustVerifyEmail on the model |
| Password reset | Broker and notifications table | Tokens are hashed; rows expire via auth.passwords.users.expire |
⚠️
Always regenerate the session on login (
$request->session()->regenerate(), which Breeze does for you) and invalidate it on logout. Skipping this leaves the application open to session fixation.Gates, policies and enforcement
class BookPolicy
{
public function viewAny(User $user): bool
{
return true;
}
public function update(User $user, Book $book): bool
{
return $user->id === $book->author_id || $user->hasRole('editor');
}
public function delete(User $user, Book $book): bool
{
return $user->hasRole('admin');
}
}
// controller: 403 automatically
$this->authorize('update', $book);
// or inside a form request
public function authorize(): bool
{
return $this->user()->can('update', $this->route('book'));
}
// Blade and JS
@can('update', $book)
<a href="{{ route('books.edit', $book) }}">Edit</a>
@endcan
@json(['canUpdate' => Auth::user()->can('update', $book)])- A policy is discovered by naming convention (
BooktoBookPolicy); register unusual pairs explicitly in a service provider. authorize()throwsAuthorizationExceptionwhich Laravel renders as 403 - never rely on hiding a button as your only check.- Use
Gate::beforefor a global admin bypass, and keep it to one line so it cannot become a policy of its own. - Deny by default: a policy method that returns nothing is a denial, and that is the right direction for an unhandled case.
Verification, reset and rate limits
class User extends Authenticatable implements MustVerifyEmail
{
use HasFactory, Notifiable;
}
Route::get('/billing', BillingController::class)
->middleware(['auth', 'verified', 'can:viewBilling,App\Models\Account']);
// throttling auth endpoints
RateLimiter::for('login', fn (Request $request) =>
Limit::perMinute(5)->by($request->input('email').'|'.$request->ip()));Throttle login by the submitted identifier as well as the IP, otherwise one attacker with many addresses freely brute-forces a single account. Notifications for reset links must be queued so a slow mail provider does not hold the request open.
FAQ
Sanctum or Passport?
Sanctum for a first-party SPA or a mobile app issuing simple personal access tokens, including cookie-based SPA auth. Passport when you must implement a full OAuth2 server with clients, scopes and grants for third parties.
Where should the authorization check go?
In the policy, called from a form request or
authorize() at the controller edge. Never in the model and never only in the view - both leave a path that calls the action without a check.Related
Building APIs with Sanctum Controllers, Blade views and Eloquent models
Last refreshed 2026-09-18.