divineomega/symfony-password-exposed-bundle
Symfony bundle that checks whether a password has appeared in a data breach, using the haveibeenpwned.com API with configurable PSR-18 client, PSR-6 cache, and PSR-17 factories. Supports PHP 7.4+ and Symfony 4.4 through 8.
Important framing: this is a Symfony bundle, not a Laravel package. It registers DI services, reads config under a password_exposed: key, and ships a Symfony Validator constraint. There is no Laravel service provider, no publishable config, no facade, no validation rule. For a Laravel codebase the honest position is: treat this as a reference implementation, not a dependency.
Adopt it directly only if:
HttpClient and Cache components — then the PasswordExposedCheckerInterface and PasswordExposed constraint save you real glue code.Look elsewhere — or write it yourself — if:
jord-jd/password_exposed library and bind it in a service provider, or implement the range query directly against Http:: and Cache::.divineomega → jord-jd) with a v1.0.0 "automated migration release." That's a supply-chain signal, not a feature.Executives — pitch the capability, not the package. "We can screen every new and changed password against hundreds of millions of breached credentials, for free, without any identifiable data leaving our infrastructure — we send five characters of a hash and receive a match count back. This is a control NIST 800-63B explicitly asks for, and it's the single highest-leverage defense against credential stuffing." Then give the honest engineering footnote: the vendor wrapper is a 1-star Symfony-only bundle with no dependents, so engineering will implement the equivalent themselves against our own HTTP client and cache. Same compliance story, no third-party dependency sitting in the authentication path.
Engineering — the mechanism is small and well-understood. SHA-1 the candidate password, request api.pwnedpasswords.com/range/{first5}, compare the returned suffixes. Cache the prefix range responses for 24h–30d and nearly all traffic disappears. Decide these before writing code: fail-open (recommended for signup conversion, with a metric so you can see it firing), a hard block on match with a friendly "choose a different password" message, a 1–2s timeout so a slow API can never stall registration, and a strict rule that neither the password nor the full hash is ever logged. On Symfony, the bundle is a legitimate shortcut. On Laravel, budget a day for a service provider, validation rule, config, and tests — and take the underlying library, not the bundle.
Security — position it as one layer, not the answer. k-anonymity means HIBP cannot recover the password from the query, so this is a low-privacy-risk control. But it stops reuse of known-breached credentials, not phishing or credential theft. Pair it with length-first policy (NIST favors length plus breach screening over composition rules) and keep MFA on the roadmap so the breach check isn't mistaken for account-takeover protection.
How can I help you explore Laravel packages today?