painlesscode/breeze-multiauth
Adds multi-authentication support to Laravel Breeze, helping you scaffold separate guards, login flows, and redirects for multiple user types such as admins and regular users with Breeze-style routes, views, and middleware.
painlesscode/breeze-multiauth is a scaffolding generator that sits on top of Laravel Breeze. Instead of hand-rolling a second (or third) guard, it generates the model, migration, config/auth.php entries, routes, controllers, and views for an additional actor — typically an "admin", "vendor", or "staff" guard alongside Breeze's default web/users guard.
The minimal install path assumes Breeze is already in place, because the package extends Breeze's stubs:
composer require laravel/breeze --dev then php artisan breeze:install blade (pick the stack you intend to extend — the package mirrors it).composer require painlesscode/breeze-multiauth --dev.multiauth:... convention and takes a guard name such as admin), because argument names are not stable across versions.php artisan migrate, then php artisan config:clear && php artisan route:clear so the newly written config/auth.php and route files are picked up.Where to look first after generation:
config/auth.php — confirm the new guards, providers, and passwords entries were actually written. This is the file that silently breaks everything if it's wrong.routes/auth.php (or a generated routes/{guard}.php) — the new login/logout/register/reset group and its middleware string.app/Http/Controllers/Auth/* — per-guard copies of Breeze's controllers, adjusted to use the new guard.app/Models/{Guard}.php and its migration — check the table name and that Authenticatable is extended correctly.First real use case: adding an admin guard so /admin/login authenticates against the admins table while /login stays on Breeze's web guard, each with its own session, dashboard, and password reset flow.
One guard per actor, never share a guard across actors. Generate one guard per distinct login surface (admin, vendor, staff). Sharing a guard across two actors collapses sessions and defeats the whole point. If two actors should share credentials, use one guard and distinguish by role instead.
Route grouping per guard. Every generated route group should carry both the guard-specific guest/auth middleware and the redirect target. A typical day-to-day pattern is:
Route::middleware('guest:admin')->group(function () {
Route::get('/admin/login', [AdminLoginController::class, 'create']);
Route::post('/admin/login', [AdminLoginController::class, 'store']);
});
Route::middleware('auth:admin')->group(function () {
Route::get('/admin/dashboard', fn () => view('admin.dashboard'));
});
The :admin suffix on guest and auth is the piece most developers forget on the first pass.
Extend the generator output, don't regenerate. Treat generated controllers/requests as starting points and edit them in place. Re-running the command after you've customised files tends to overwrite or duplicate routes; copy a generated file aside before experimenting.
Separate Blade layouts per guard. Breeze ships one layouts/app.blade.php and one layouts/guest.blade.php. Multiauth is much easier to reason about with a parallel layouts/admin tree so nav, branding, and logout forms never cross wires. The generated login views already point at the guard's own form action — keep that pattern when customising.
Redirect logic comes from the guard's provider entry. After login, RedirectIfAuthenticated and the login controller both need to know where each guard lands. Register intent in one place (a small mapping in a service provider or a config key) rather than hard-coding URLs across controllers.
Testing pattern. Use $this->actingAs($admin, 'admin') — the second argument is non-negotiable. Feature tests that omit it authenticate on the web guard and will fail middleware you thought you'd installed.
Age is the biggest risk. Last release was February 2022. Breeze restructured its stubs (and Laravel itself moved through 9 → 10 → 11) after that, so generation against a modern Breeze install may produce paths or class names that no longer match. Pin your Laravel and Breeze versions, or treat the package output as a reference to copy rather than something you trust blindly. Check the repo's issues before committing to it in a long-lived project.
License is NOASSERTION. That means GitHub/packagist couldn't classify it. Before shipping, open LICENSE / composer.json in the repo and confirm it's compatible with your commercial use — don't assume MIT just because Breeze is.
Password brokers must be registered, not just guards. A new guard alone does not give you password resets. The generated config/auth.php should add a passwords.{guard} entry pointing at a passwords table with a user_type or a separate table. If reset emails 404 or throw "password broker not defined", this entry is missing.
Route name collisions. Breeze names routes login, register, password.request, etc. Generated guards add prefixes inconsistently between versions. Audit php artisan route:list after generation and rename to admin.login, admin.password.request, etc., or route('login') will resolve to whichever file loaded last.
Config and route cache trap. config/auth.php changes don't take effect until config:clear (and in production, until config:cache is rerun). Route changes need route:clear. Add both to your deploy script the first time a guard "works locally but not in staging."
Session cookie collisions. Two guards on the same domain share the session cookie by default, which is usually what you want. If you truly need isolated sessions (e.g. admin on a subdomain), set SESSION_DOMAIN and cookie names per guard — otherwise logging into admin kicks the user guard's session.
Email verification does not follow the guard automatically. If admins need MustVerifyEmail, implement it on the generated Admin model and add verified:admin middleware manually. The generator does not wire this up for you.
Extension point: publish the stubs. If the command supports --tag publishing, publish the stubs and edit them, rather than patching generated files every time. That's the cleanest way to keep the generator useful across multiple guards in a project.
Debugging checklist when a new guard "does nothing": route:list for the guard's routes → config('auth.guards') via php artisan tinker → confirm the middleware alias resolves → confirm the provider's model class exists and extends Authenticatable → clear caches. Ninety percent of multiauth bugs are one of those five.
How can I help you explore Laravel packages today?