Answer to the unresolved decision
Confirmed: Laravel does not automatically purge expired rows from password_resets. The framework only checks age on use via config/auth.php passwords.users.expire. A scheduled cleanup job is the standard viable approach. An automatic purge on every reset attempt is possible but adds a write to the hot path.
Confirmed facts for this case
- Default expiration is 60 minutes, set in
config/auth.php under passwords.users.expire.
- No built-in per-user or per-role expiration setting exists.
- Expired rows persist until manual cleanup; the framework does not delete them.
- Token validity is independent of the remember me cookie lifespan.
Likely explanation vs confirmed behavior
Likely: developers expect stale tokens to disappear automatically. Confirmed: Laravel treats expiration as a validation check, not a data lifecycle. Deletion is left to the application.
Trade-offs: automatic purge on access vs scheduled cleanup
- Automatic purge on reset attempt: reduces table growth immediately, avoids stale data being considered, adds a DELETE/UPDATE write to the reset request path and increases latency under load.
- Scheduled cleanup: keeps the reset flow fast, moves load to off-peak, requires scheduler reliability and monitoring. Stale rows accumulate between runs.
For most apps a scheduled job is preferred. Automatic purge can be added as a small guard in the broker if table growth is a measured problem.
Coordinating token expiry with remember-me
They are managed by different subsystems and cannot be automatically linked. Consistent UX is achieved by intentional alignment and explicit invalidation.
- Choose comparable durations for
passwords.users.expire, session lifetime and remember cookie lifetime, and document the relationship.
- A password reset does not automatically invalidate existing remember sessions. If you require consistency, invalidate sessions and remember tokens on successful password change for the affected user.
High-security lifespan without degrading UX
- Reduce
passwords.users.expire to 15-30 minutes for sensitive contexts.
- Enforce single use by deleting the row on successful reset.
- Add rate limiting and email throttling for reset requests.
- Require recent authentication or additional verification for high-privilege accounts.
- Communicate the short window in the reset email and UI to reduce support load.
Steps needed for this case
- Set the window: edit
config/auth.php passwords.users.expire to desired minutes, then clear config cache.
- Schedule cleanup: add a recurring command that deletes rows older than the expire window, e.g.
DELETE FROM password_resets WHERE created_at < NOW() - INTERVAL 'X minutes', run via app/Console/Kernel.php schedule.
- Verify scheduler is running via cron and logs.
Verification: inspect the loaded passwords.users.expire value after cache clear, query password_resets for rows older than the window in staging, and check scheduler logs and reset success/failure rates after change.
One diagnostic that changes the recommendation: is the Laravel scheduler running via cron on the production host? Without a reliable scheduler, scheduled cleanup will not execute and table growth will be unbounded.