dejwcake/admin-auth
Admin authentication for Laravel. Adds a dedicated admin guard with login, logout, and password handling, plus middleware to protect admin-only routes. Keeps admin sessions separate from regular user sessions so back-office access stays isolated, simple to configure, and easy to drop into existing a...
Two independent things live in this package: the ApplyUserLocale middleware that reads the authenticated user's language attribute and calls app()->setLocale(), and (as of 2.1.0) a hard dependency on spatie/laravel-permission ^8.3. Start with the middleware, then deal with the dependency.
web group in bootstrap/app.php (Laravel 11+) or app/Http/Kernel.php, after StartSession and after your auth middleware so $request->user() is populated.language attribute on your users table (typically a string(5) holding an ISO code like en or pt_BR), and add it to $fillable so profile forms can update it.language) and what it does when the attribute is null — you need to know whether it leaves the app default intact or resets it.__('messages.greeting') and checking the output flips after saving the preference.composer require spatie/laravel-permission:^8.3, resolve any conflicts from other packages still pinning ^7, run your migrations and full test suite, and only then re-verify the locale behaviour. The middleware code is unchanged in 2.1.0, but your app's permission layer now moves with it.Enum casts (2.0.4+). If you model languages as a PHP enum with a backed string, cast it on the model:
protected function casts(): array
{
return ['language' => Language::class];
}
Before 2.0.4 this passed the enum object straight into setLocale() and blew up (or silently misbehaved). Now the middleware detects a non-string value and skips it. That means the enum cast works only if the middleware itself unwraps it — if it doesn't, you'll get the app default instead of the user's choice. Test this explicitly after upgrading.
String column, resolve at the edge. The most predictable pattern is to keep language a plain string and validate it against an allowlist in a FormRequest. Store canonical codes only; do the enum conversion in the UI layer, not the persistence layer. This sidesteps the non-string question entirely — and, usefully in 2.1.0, it also matches the BackedEnum|string shape Spatie now expects at its own boundaries.
Ordering with other locale sources. Middleware order defines precedence. A common setup: route-prefix locale (/{locale}/...) wins, then user preference, then config('app.locale'). If you want user preference to win, register ApplyUserLocale after the route-locale middleware; if route should win, reverse it. Don't rely on the package to arbitrate — be explicit.
Per-request only. setLocale() is request-scoped. Anything that runs outside the HTTP stack (queued jobs, artisan commands, scheduler) falls back to config('app.locale'). In jobs that generate user-facing text (emails, notifications), call app()->setLocale($user->language) yourself at the top of handle().
Blade and validation. Once the locale is set, __(), @lang, and validation messages resolve automatically. Publish Laravel's lang/ directory (php artisan lang:publish) and keep translation keys complete — a missing key renders the raw key, which looks broken in a language you don't read yourself.
Permission upgrade (2.1.0). This package ships no code for the Spatie bump, so the upgrade is entirely yours to sequence:
composer require spatie/laravel-permission:^8.3
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="permission-migrations"
php artisan migrate
php artisan optimize:clear
If your composer.json pins ^7.4 directly, update that constraint too — otherwise Composer will happily leave you on v7 while the lockfile disagrees with the package's requirement. If you implement the Role / Permission contracts yourself (custom models or a custom Role class), you must widen the signatures to findByName(BackedEnum|string $name, ?string $guardName = null) and findOrCreate(BackedEnum|string $name, ?string $guardName = null) — PHP will fatal on the interface mismatch otherwise. If you just use Spatie's shipped models, nothing to change. Where you do write role/permission lookups, prefer passing your enum directly now instead of calling ->value at every call site.
Testing the bump. Add a test that assigns a role via the enum path and one that assigns via a string, and assert hasRole() for both. That catches the contract mismatch and any caching-regression from the v8 upgrade in one shot.
language is an enum, an int, or an array, you get no exception — just the fallback locale. That trades a crash for a quiet regression. Add an assertion in your test suite that a user with a non-default language actually sees non-default output; don't assume the middleware did its job because nothing threw.auth means $request->user() is null and nothing happens. Placing it inside the web group but in a route that uses a different guard means the wrong user (or none) is read.Accept-Language negotiation, if you want it, is yours to add — this middleware is about the stored user preference.setLocale() covers translations, but Carbon and NumberFormatter need their own attention — set Carbon::setLocale() alongside, and check PHP's intl extension is installed on the target environment if you use NumberFormatter.lang/{locale} directory; Laravel just falls through to the fallback locale per-key. Keep the allowlist of storable languages in sync with the directories that actually exist.config/route/permission caches after deploying, and deploy the constraint change in the same commit as the lockfile bump so no environment ends up mid-upgrade.Role or Permission model, the failure mode is a PHP fatal at autoload/reflect time — noisy, at least. Grep for implements.*Role and implements.*Permission before you deploy.composer require this package and then go looking for its tests to understand edge-case behaviour (especially the enum path), they aren't there — .gitattributes export-ignores tests, Docker, and QA config, and 2.1.0 removed a stale phpmd.xml entry from that list without restoring anything. Clone the repository from source if you need to read the test suite.$user->language — or set the locale yourself and drop the middleware.AGENTS.md/CLAUDE.md were added upstream in 2.0.4 and describe the dev setup (MariaDB 12.3, Testbench). They exist only in the source repo, not in the dist archive — same as the tests.How can I help you explore Laravel packages today?