A few months ago I built my first Filament plugin, Filament Renew Password. It does two things: force a password change after a defined period or other criteria, and force a password change on first login when an account was created with a temporary password for example. It just crossed 100 000 downloads on Packagist, which surprised me, since the problem it solves felt narrow at the time.
When a plugin that does something this specific reaches 100k downloads, it usually means the problem it solves is more common than it looks. That made me curious about why so many projects need this, and whether the requirement behind it still holds up.
Writing this article, I went looking for what CNIL and NIST currently say about password renewal, mostly out of curiosity about whether the requirement I keep seeing on client projects still holds up. It turns out only half of it does.
What I keep being asked for
Several client PSSI (politique de sécurité des systèmes d'information in France) documents I've worked from over the years contain the same line: passwords must be renewed every 90 days, no exceptions, for every user. It's treated as a baseline security requirement, the kind of thing nobody questions because it's been standard practice for so long.
What CNIL, ANSSI and NIST actually say now
Both have moved away from that position, and more recently than I expected. NIST's SP 800-63B Revision 4, finalized in 2024, explicitly states that organizations shall not require periodic password changes without a specific reason, like a suspected compromise. The ANSSI made the same call earlier, in 2021, no longer recommending periodic renewal for standard user accounts. The CNIL followed, limiting the periodic renewal requirement to administrator accounts only, accounts with elevated privileges that typically warrant stronger authentication than a password alone anyway.
The reasoning behind the shift is consistent across all three: forced periodic rotation doesn't make accounts safer, it makes passwords more predictable. Users asked to change a password they already know well tend to make the smallest change that satisfies the rule, incrementing a number, swapping a season, appending the new year. The password technically changes. Its actual strength against an attacker who has seen a previous version barely does.
So the blanket 90-day rule that shows up in PSSI after PSSI isn't current best practice anymore. It's a holdover from a doctrine that both French and American standards bodies have since revised.
| Requirement | CNIL | ANSSI | NIST |
|---|---|---|---|
| Periodic password renewal for all users | ❌ | ❌ | ❌ |
| Renewal after compromise | ✅ | ✅ | ✅ |
| First login temporary password | ✅ | ✅ | ✅ |
| Privileged accounts | ✅ | ✅ | ❌* |
*NIST recommends stronger authentication (phishing-resistant MFA) for privileged accounts rather than periodic password rotation.
Why it's still in every PSSI I see
None of this means client requirements are wrong to ask for what they ask for, security policy documents tend to lag behind the standards they're built on, sometimes by several years, and updating one isn't a small undertaking once it's referenced across audits, contracts, and internal procedures. I'd guess most of the 90-day clauses I still run into were written when periodic renewal was the recommended baseline, and nobody has gone back to revise them since CNIL and ANSSI changed their position.
There's also a difference between a security policy being outdated and being wrong to enforce regardless. A client's PSSI is what I build against. If it says 90 days, I implement 90 days, whether or not the underlying recommendation has moved on. That's not really a technical decision to make on a client's behalf.
What still holds up
Two specific cases haven't changed, and they're the two the plugin actually addresses.
First-login password change , for accounts created by an administrator with a temporary password, remains explicitly recommended by the CNIL. The reasoning is straightforward: a password assigned by someone else, sent over email or handed over directly, has already been seen by at least one other person. Forcing a change on first use closes that exposure immediately.
Periodic renewal for privileged accounts also remains recommended by both CNIL and ANSSI, specifically because of what's at stake if that one account is compromised, alongside stronger authentication measures such as MFA. The general "don't force rotation" guidance was always about standard user accounts, not administrative ones.
So the plugin's two features end up mapping cleanly onto the two cases where forced renewal is still the right call, and not particularly onto the blanket 90-day-for-everyone rule that's actually more common in the PSSI documents I work from.
The plugin doesn't decide which case applies. That's the developer's call, based on what the project actually needs. Here's how to make that distinction explicit in code.
Making the distinction in code
The plugin doesn't hardcode when renewal is required. It checks a single method, needRenewPassword(), via a middleware that redirects to the renewal screen whenever it returns true:
class RenewPasswordMiddleware
{
public function handle(Request $request, Closure $next): mixed
{
$user = $request->user();
if (
$user
&& in_array(RenewPasswordContract::class, class_implements($user))
&& $user->needRenewPassword()
) {
$panelId = Filament::getCurrentPanel()->getId();
return Redirect::guest(URL::route("filament.{$panelId}.auth.password.renew"));
}
return $next($request);
}
}
The default trait shipped with the plugin checks two conditions, time-based expiry and a forced-renewal flag:
public function needRenewPassword(): bool
{
$plugin = RenewPasswordPlugin::get();
return
(
! is_null($plugin->getPasswordExpiresIn())
&& Carbon::parse($this->{$plugin->getTimestampColumn()})->addDays($plugin->getPasswordExpiresIn()) < now()
) || (
$plugin->getForceRenewPassword()
&& $this->{$plugin->getForceRenewColumn()}
);
}
Used as-is, this applies the same expiry rule to every user. To match what CNIL and ANSSI actually recommend, that method can be overridden on the User model so periodic expiry only applies to privileged accounts, while the forced first-login change still applies to everyone:
public function needRenewPassword(): bool
{
$plugin = RenewPasswordPlugin::get();
$periodicExpiryApplies = $this->hasRole('Backend')
&& ! is_null($plugin->getPasswordExpiresIn())
&& Carbon::parse($this->{$plugin->getTimestampColumn()})->addDays($plugin->getPasswordExpiresIn()) < now();
$forcedFirstLoginApplies = $plugin->getForceRenewPassword()
&& $this->{$plugin->getForceRenewColumn()};
return $periodicExpiryApplies || $forcedFirstLoginApplies;
}
The interesting part wasn't discovering that password rotation recommendations had changed. It was realizing that many production systems still legitimately implement policies written years earlier. Good software shouldn't hardcode today's best practice. It should make the policy explicit, configurable, and easy to adapt as recommendations evolve.
If you're maintaining a password policy that still mandates periodic renewal for every user account, it might be worth checking whether that requirement reflects a deliberate, current decision, or simply hasn't been revisited since CNIL and ANSSI moved on from it in 2021 and 2022.
Filament Renew Password is included in all three Filament Mastery starter kits, the Backend Starter, the Multipanel Starter, and the Multi-Tenant Starter, with the first-login flow already wired up for panels.
I'd be curious what others are seeing on their own projects. Are you still implementing blanket periodic renewal because a client's PSSI requires it, have you pushed back successfully, or has this simply not come up yet? Drop a comment, I'd like to know how widespread this still is outside what I've personally run into.

Top comments (0)