nowo-tech/password-policy-bundle
A Symfony bundle for enforcing configurable password policies, including length and complexity rules, expiration, reuse prevention, and customizable validation messages. Integrates with Symfony's validator and security components to help keep user credentials secure with minimal setup.
Verdict: This is a Symfony bundle, not a Laravel package. It cannot be installed into a Laravel application as-is. Its value to a Laravel-based roadmap is limited to the domain logic inside it — validation rules, history/expiry policy services — which would have to be lifted out and re-wired into Laravel's container, validation, and auth events. Treat this as "reference implementation / source of design," not a dependency.
Architecture fit
Integration feasibility
| Component | Portable to Laravel? | Notes |
|---|---|---|
| Validation rules / policy evaluators | Yes, with a rewrite of rule entry points | Map to a custom Rule object or a Validator extension |
| Password history service | Yes — logic, not wiring | Needs our own repository/model + migration |
| Expiry policy | Logic only | Enforcement hook does not exist in Laravel; we build it |
| Hasher / encoder integration | No | Symfony's PasswordHasherFactoryInterface ≠ Laravel's Hash manager; formats are both password_hash-compatible but the abstraction, rehash-on-login, and algorithm config differ |
| DI extension, compiler pass, config tree | No | Symfony-only |
| Form type, Twig templates | No | Rebuild as Blade/Livewire if UI prompts are needed |
| Symfony security event hooks | No | We hook Illuminate\Auth\Events\Login + middleware instead |
Net: roughly 60–70% of the package's codebase is unusable in our stack, and the 30% that is usable is the 30% that is cheapest to write from scratch.
Technical risk
Key questions
Password rule plus a history check, at a fraction of the risk.Stack fit
Poor as a dependency, acceptable as a design reference. Laravel already ships most of the value: the Password validation rule (min length, uncompromised check via HaveIBeenPwned, mixed case, numbers, symbols) covers strength and blocklisting. The only genuine gaps versus this package are history and expiry — two features, both of which are ~a table, a service, and a login hook.
Recommended path (in order of preference)
| Option | Effort | Risk | Recommendation |
|---|---|---|---|
A. Native implementation: Password rule + password_histories table + login listener + middleware gate |
4–7 dev-days | Low; fully owned, no upstream | Preferred |
B. Vendor the policy core: copy the portable services into app/Support/PasswordPolicy, adapter over Hash, own migrations |
5–8 dev-days | Medium; we inherit code we did not write and must review it | Acceptable if the team wants a head start |
| C. Depend on the bundle via a shim/compat layer | Not viable | High | Reject — no Laravel integration exists and we would author all of it anyway |
Given 0 stars and 0 dependents, Option C is not defensible in a review. Option A is also faster than B once you account for reading and adapting unfamiliar code.
Migration path (if Option A/B chosen)
password_histories (user id, hash or fingerprint, created_at), password_changed_at on users. Decide fingerprint-vs-hash before writing the migration — it is a one-way door for purge.validate(string $plain, User $u): void and isExpired(User $u): bool. No framework coupling.Login listener to flag expired credentials; middleware on authenticated routes to redirect to a change-password screen and allow-list logout/support routes. This is the part the package does not do for us in Laravel and the part most likely to be underestimated.password_changed_at; treat null as "not expired" or "force change at next login" — a product decision, not a technical one. Ship behind a feature flag, ramp per-tenant.Compatibility
Illuminate\Validation\Rules\Password (8.x+) for Option A.Hash::check against stored hashes, never string equality.Sequencing Spike → schema decision → policy service + tests → login listener → route middleware → rollout flag → docs/runbook. Do not start with the UI; the enforcement gap is the risk, the screen is the easy part.
Maintenance
Support
Scaling
(user_id, created_at) and define a retention cap (e.g. last 5) rather than unbounded rows.Failure modes
| Failure | Blast radius | Detection | Mitigation |
|---|---|---|---|
| Middleware misses a route → expired password still usable | Silent policy bypass, audit finding | Route-coverage test asserting all authenticated routes are gated | Allow-list model, not deny-list; automated test enumerating routes |
| Hash-driver change or rehash-on-login breaks history comparison | Users can reuse old passwords; logins unaffected | Integration test with two drivers | Compare via Hash::check only; test both drivers in CI |
Null password_changed_at on legacy users |
Bulk forced reset on rollout | Pre-rollout count query | Decide null semantics explicitly; flag-gated rollout |
| History store retained indefinitely | Data-protection / retention exposure | Schema review, DPO sign-off | Retention cap + purge job from day one |
| Upstream abandoned (if depended upon) | Stuck on old framework versions | Quarterly dependency review | Fork-or-replace escape hatch documented |
| Timezone/DST bug in expiry comparison | Users locked out at boundary | Boundary tests around midnight/DST | Store UTC, compare UTC, test the transition |
Ramp-up
How can I help you explore Laravel packages today?