samasend/laravel-needs-auto-rehash
Laravel Needs Auto Rehash automatically detects outdated password hashes and rehashes them after successful login, keeping user credentials aligned with your app’s current hashing algorithm and cost settings with minimal configuration.
What it is. A small Laravel service provider that listens on the framework's authentication events and, when a successfully authenticated user's stored hash no longer matches the current hashing configuration (Hash::needsRehash), transparently rehashes the plaintext credential and persists the new hash. It replaces a 3–5 line block that Laravel's own starter kits (Breeze, Jetstream, Fortify) currently ask developers to write by hand in the login path.
Architecture fit. The design is idiomatic Laravel: event listener + service provider, auto-discovered, zero-config. It does not patch the framework, wrap the Hash manager, or replace the guard, which is the right shape for this concern and keeps blast radius small. The concern is genuinely cross-cutting (every authentication entry point — web login, API token issuance, password reset, impersonation), so an event-driven listener is a better fit than duplicating the check in each controller. Conceptually this is a good architectural match.
Integration feasibility. Technically trivial — one composer require, no config file, no migrations, no published assets. In practice, the binding constraint is the release timeline. Last release was 2023-02-20 and the repository is archived, so the illuminate/* constraints almost certainly stop at Laravel 9/10. On Laravel 11/12 expect a dependency-resolution conflict, and note that the reason this package is interesting — Laravel 11 raised the default bcrypt cost from 10 to 12 — is exactly the scenario it can no longer be trusted to run in. The package is functionally complete, but functionally complete is not the same as installable.
Technical risk. This is a security-adjacent package sitting directly on the credential path, and that raises the bar considerably:
updated_at, other columns, observers, audit columns). Whether the package does a targeted update() or a full save() is the single most important thing to read in source.Validated/Attempting events do; Login does not). Verify the listener binds to an event that actually receives plaintext, and confirm it cannot fire on failed attempts.Key questions to answer before any decision.
save(), and how does it behave with observers, updated_at, and concurrent writes?composer.json constraint set, and does it resolve against our target Laravel version without --with-all-dependencies hacks?Assessment. The architecture is sound and the problem is real, but the artifact is not adoptable as a third-party dependency: archived status on a credential-path package is a hard stop for most security review processes. The correct TPM outcome is not "reject the idea" — it is "extract the pattern, reject the dependency." Use the package as a reference implementation and own ~50 lines internally.
Stack fit. Excellent, in the narrow sense. It assumes nothing beyond Laravel's Hash facade and the event dispatcher, both stable across majors. There is no framework-internal coupling, no facade swapping, no middleware ordering dependency. Any Laravel application with a standard guard-based auth flow can host the logic.
Migration path — recommended: internal reimplementation, not adoption.
AutoRehashPassword listener in-house. Bind to the auth event carrying plaintext credentials, guard on successful authentication, call Hash::needsRehash(), and persist the hash with a targeted forceFill(['password' => ...])->save() or a scoped query update. Wrap in try/catch that logs and swallows — a failed rehash must never fail a login.If for organizational reasons a dependency is preferred, the only defensible option is a vendored fork under our own namespace with our own release cadence and compatibility pins — at which point the effort delta versus writing it ourselves is negligible, and you carry the maintenance anyway.
Compatibility. Do not composer require the upstream package against Laravel 11+. The illuminate/support constraint will not resolve, and forcing it risks installing a package the framework cannot autoload correctly. If forking, update constraints to ^10.0|^11.0|^12.0 and run the suite on each major in CI.
Sequencing. Do not do this as a standalone change. The natural trigger is a hashing-parameter change (cost factor bump, algorithm migration, HASH_DRIVER switch). Bundle the listener work into that same initiative so it ships with the change that makes it necessary, and so the rollout plan explicitly accounts for the write amplification it introduces. Ship listener first, verify on a canary cohort, then flip the cost parameter.
Blast radius. Contained to the auth path if implemented as described. Explicitly out of scope: rehashing admin-set passwords outside a user's own login, rehashing API tokens, and any attempt to rehash on behalf of users who never log in again (see below).
Maintenance. As a dependency: unbounded and unowned — an archived repo generates no upgrades and answers no pages, so the maintenance burden is silently transferred entirely to us at the worst possible moment (a framework upgrade). As an internal ~50-line listener: near-zero, a routine code-ownership cost with no external dependency to track. This asymmetry is the core of the recommendation. Do not let a trivially small dependency become a supply-chain liability on the authentication path.
Support. No upstream support, no issue triage, no maintainer to escalate to. Internally, the listener is well within the competence of any mid-level Laravel engineer to debug — the mechanism is a single Hash::needsRehash call and a save. Failure diagnosis is straightforward if it is instrumented, which is why the logging requirement in Phase 4 is not optional.
Scaling — the significant operational consideration. After a cost-factor increase, every returning user whose hash predates the change triggers a synchronous write on their next successful login. Three consequences:
users table during the migration window, concentrated on login. Expect elevated row-level contention on users rows and increased updated_at churn, which can disturb anything keyed off that column.Mitigation: canary rollout by cohort, stagger the parameter flip, watch DB write latency and login p99 during the window, and treat the listener as a no-op-after-convergence path so steady-state cost is one cheap needsRehash call.
Failure modes and mitigations.
| Failure | Impact | Mitigation |
|---|---|---|
| Listener throws during login | Login outage — highest severity | try/catch, log, never propagate |
Full-model save() clobbers concurrent writes |
Data loss on user record | Targeted column update only; feature test for it |
| Rehash fires on failed authentication | Wasted writes; potential info leak | Guard on successful-auth event only |
| DB write fails / times out | Login succeeds, hash not upgraded | Accept and retry next login; log for visibility |
| No telemetry | Migration progress invisible; silent no-ops | Counter/log per rehash; dashboard the rate |
| Package unresolvable on newer Laravel | Blocked framework upgrade | Internal implementation removes this entirely |
Ramp-up. Negligible for the internal approach — roughly half a day for an engineer familiar with Laravel's auth lifecycle, including tests and instrumentation; the conceptual payload is one well-known framework method. The reading time for the reference implementation is under an hour. Contrast with adopting the package: a security review that will almost certainly block on archived status, an unknown amount of source auditing to answer the persistence-semantics question, and a support argument that has no good answer.
Recommendation. Do not take a production dependency on an archived package on the credential path. Reimplement the pattern internally (MIT-licensed reference, ~50 lines, fully tested and instrumented), ship it alongside the next hashing-parameter change, and canary the rollout with explicit monitoring of login latency and users write volume. The package is a useful design reference and a poor operational commitment.
How can I help you explore Laravel packages today?