bbs-lab/laravel-password-rotation
| Install | |
|---|---|
composer require bbs-lab/laravel-password-rotation |
|
| Latest Version: | v1.3.0 |
| PHP: | ^8.2 |
| Laravel: | 11 · 12 · 13 |
| License: | MIT |
| Last Updated: | Sep 24, 2026 |
| Links: | GitHub · Packagist |
Laravel Password Rotation
Password rotation for any Laravel application — no admin panel required. Force any authenticatable model to rotate its password every N days, with reuse prevention, a first-login gate, an expiry-warning helper and a redirect middleware. No Nova, no Filament.
This is the shared core behind the two admin-panel twins:
bbs-lab/nova-password-rotation— Laravel Novabbs-lab/filament-password-rotation— Filament
The rotatable subject is not tied to the User model: everything keys off the
MustRotatePassword interface on whatever authenticatable you gate, and the password history is
polymorphic, so any model works.
Requirements
- PHP
^8.2 - Laravel
^11.0 || ^12.0 || ^13.0
Installation
composer require bbs-lab/laravel-password-rotation
The service provider auto-registers via Laravel package discovery.
The password_histories table migrates automatically. Publish the config and the
(review-before-run) user-column migration, then migrate:
# Config
php artisan vendor:publish --tag=laravel-password-rotation-config
# Adds the password_changed_at column to your authenticatable's table.
# Rename/edit it first — your rotatable model rarely lives on "users".
php artisan vendor:publish --tag=laravel-password-rotation-user-migration
# Optional — only to customise the shipped password_histories migration
# php artisan vendor:publish --tag=laravel-password-rotation-migrations
# Translations (en, fr) — only to customise the messages
php artisan vendor:publish --tag=laravel-password-rotation-translations
php artisan migrate
Usage
Make a model rotatable
Implement MustRotatePassword and use the RotatesPassword trait on any
authenticatable model. Nothing is tied to User — the trait keys off the
interface and the history table is polymorphic.
use BBSLab\LaravelPasswordRotation\Concerns\RotatesPassword;
use BBSLab\LaravelPasswordRotation\Contracts\MustRotatePassword;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable implements MustRotatePassword
{
use RotatesPassword;
}
The trait stamps password_changed_at whenever the password changes, records
each hash for reuse prevention, and dispatches PasswordRotated. It exposes:
passwordHasExpired(): bool— past the rotation window (or never set, whenforce_on_first_loginis on)passwordIsExpiring(): bool— inside the warning window (warn_days)passwordExpiresAt(): ?CarbonInterface
Force expired users to a change screen
Add EnsurePasswordIsNotExpired to the middleware group that guards your app
and point redirect_route at your own password-change screen:
// config/laravel-password-rotation.php
'redirect_route' => 'password.rotate',
// keep users able to sign out while their password is expired
'except_routes' => ['logout'],
Route::middleware(['web', 'auth', EnsurePasswordIsNotExpired::class])->group(function () {
// ...your app
});
An expired user is redirected to redirect_route on every request except that
route and anything in except_routes. Exempt your logout route (and, if the
change form POSTs to a different route, that route too) or the user is trapped
with no way out. The middleware reads the default guard's user; on a custom
guard, wire it accordingly.
Bypass rotation per request (e.g. SSO users)
Some users must never be forced through the local change screen — SSO users, for
instance, whose password lives in the identity provider. Register a bypass
callback via the PasswordRotation facade (in a service provider's boot()).
It receives the request and the expired user, and returning true lets that
request through:
use BBSLab\LaravelPasswordRotation\Facades\PasswordRotation;
use Illuminate\Http\Request;
PasswordRotation::bypass(
fn (Request $request) => $request->hasSession()
&& $request->session()->get('sso') === true,
);
- Register the callback in code, not in the config file — a
Closurethere would breakphp artisan config:cache. - Guard
hasSession()before reading the session so the callback is safe if the middleware ever runs outside thewebgroup (a token/API guard has no session). Store the SSO marker as a real boolean (session(['sso' => true])). bypass()may be called several times; the request is exempt as soon as any callback returnstrue.
Prevent password reuse
Add the PasswordNotReused rule to your change-password validation:
use BBSLab\LaravelPasswordRotation\Rules\PasswordNotReused;
$request->validate([
'password' => ['required', 'confirmed', Password::defaults(), new PasswordNotReused($request->user())],
]);
It rejects the current password and the last history_count hashes.
Report on rotation status
php artisan password-rotation:report # expired / expiring-soon accounts
php artisan password-rotation:report --all # every account
The command scans the classes listed under models in the config.
License
The MIT License (MIT). Please see License File for more information.
Related Packages
Monitors Livewire update requests for deserialization attack patterns (CVE-2025-...
A Laravel Middleware to filter user inputs from XSS and iframes and other embed...
Version History
| Version | Released | PHP | Laravel | License |
|---|---|---|---|---|
| v1.3.0 | ^8.2 | ^11.0 || | MIT | |
| v1.2.1 | ^8.2 | ^11.0 || | MIT | |
| v1.2.0 | ^8.2 | ^11.0 || | MIT | |
| v1.1.0 | ^8.2 | ^11.0 || | MIT | |
| v1.0.0 | ^8.2 | ^11.0 || | MIT |