graham-campbell/passwd
A Laravel password validator and generator that checks strength, requires letters, numbers, and symbols, rejects common or weak passwords, and generates secure random passwords via a simple, configurable service provider integration.
Architecture fit. graham-campbell/passwd is a deliberately thin manager over PHP's native password_hash/password_verify, split into hashers (produce a hash) and checkers (verify one, potentially in multiple legacy formats). It binds into Laravel's container via a service provider and exposes a Passwd facade, registered the 4.1 way in app/config/app.php. Architecturally it is sound and small — a few classes, one config file, one extension seam. The fit problem is not design, it is era: the package targets Laravel 4.1, depends on illuminate/* from that generation, and its last tag is v0.4.0-alpha. It fits an existing 4.1 application and essentially nothing else. The "framework-agnostic" claim in the description is aspirational — the shipped integration is Laravel-specific.
Integration feasibility. Technically feasible: PSR-4 autoloading, added unit tests, and a source surface you can read end-to-end in an afternoon make it auditable. The gate is a composer require "graham-campbell/passwd:0.4.0-alpha" --dry-run against a throwaway branch. If it conflicts with your illuminate/* constraint, the evaluation ends there — Laravel's own Hash facade already wraps the same primitives and you have lost nothing. Pin the exact tag; never a caret range on a pre-1.0 security-adjacent package.
Technical risk. High, and concentrated in provenance rather than code. The upstream lives under a GrahamDeprecated namespace with zero dependents and an alpha tag — meaning no CVE patches, no PHP-version compatibility fixes, no issue-tracker answers, and no external validation that anyone has run this in production. The API is not a contract; config keys and checker registration can move between tags. Build-time and platform hazards compound it: Argon2 availability depends on how PHP was compiled (defined('PASSWORD_ARGON2I') must be checked before it is set as default), and bcrypt silently truncates at 72 bytes.
Key questions for the decision owner.
Hash / password_* does not? If the answer is "none," stop.Stack fit. Assume Laravel 4.1 only. On any modern Laravel, use the Hash facade and close the ticket. The package's value is bounded to two scenarios: (a) an existing 4.1 app with a specific legacy hash format to verify, and (b) a migration script that must accept hashes from a legacy CMS or forum. Everything else is a worse-maintained substitute for a vendor primitive.
Migration path. Wrap it before you wire it. Define a single PasswordHasher interface with hash() and check(), bind one implementation in the container, and let the application depend on the interface, not the Passwd facade. This costs roughly twenty minutes and reduces the blast radius of abandonment to one class and one binding. Enforce the policy in one place — a CredentialService or model mutator — so cost factors and rehash rules live in a single file.
For the legacy case specifically, use verify-any, rehash-on-success: try the legacy checker, and on success rehash with the current hasher and persist, with the plaintext in memory only for that instant. Gate the legacy path behind a legacy_hash = 1 flag set at import time, so you are not running three checkers on every login for the whole user base. When the flag is cleared, delete the dead checker.
Where possible, convert this into a one-time backfill or account-claim-time script rather than a login-path dependency. That reframing removes the package from your production auth path entirely and is the strongest recommendation in this assessment.
Compatibility. Pin 0.4.0-alpha, commit composer.lock, and verify defined('PASSWORD_ARGON2I') before setting Argon2 as default — otherwise you discover the failure on the signup path. Watch the bcrypt 72-byte ceiling: either accept it explicitly, pre-hash (SHA-256 then bcrypt), or move to Argon2, and record the choice. Copy configuration idioms from the package README, not modern Laravel blog posts; 4.1 syntax differs in service-provider registration and container sugar.
Sequencing.
src/ in full — manager, Hashers/, Checkers/, config, service provider — and confirm singleton bindings.password_hash($plain, PASSWORD_BCRYPT, ['cost' => N]) and password_verify($plain, $hash). If you cannot articulate that in a paragraph, you are coupled harder than the package deserves.Maintenance. You own this, whether or not it is stated in a ticket. Archived upstream plus alpha tag plus security-adjacent scope means the maintenance plan is a fork plan: named owner, review cadence, and a documented patch process. Budget for it explicitly in the risk register rather than absorbing it silently into "engineering overhead."
Support. Zero. No maintainer answers, no community to consult, no upgrade guide. All operational knowledge is tribal and lives with whoever ran the spike. Mitigate with a short written runbook: what the config controls, how to change cost, how to add a checker, and how to leave.
Scaling. Hashing is CPU-bound. Under login bursts the failure mode is a saturated worker pool and a slow login queue — degradation, not a crash, which makes it easy to miss until it is a customer-visible incident. Benchmark Passwd::hash() against production-class hardware and target roughly 100–250 ms; resist the temptation to lower cost in response, which is a straight security downgrade. Capacity-plan the auth surface separately from general request throughput.
Failure modes to enumerate up front.
setPasswordAttribute mutator re-firing on an already-hashed value → the user who logged in today is locked out tomorrow. Verify before hash, and confirm the save path does not re-hash.== or hashes leaking into logs, exception traces, or toArray() API responses. Use check() (timing-safe), add password to $hidden on every model that carries one.Ramp-up. The code surface is small enough for one engineer to review in an afternoon; the cost is not learning the package but learning the 4.1 idioms it lives in, which most current developers will not have touched. Onboarding overhead is therefore mostly the "why do we have this, and when do we remove it?" conversation — which is precisely why the adapter boundary and the written exit plan matter more than the library's feature list.
TPM bottom line. Treat this as a risk-register entry with a named decision owner, not a feature ticket. Approve it only for a bounded legacy verification or migration use case, pinned, wrapped, flagged, and scheduled for deletion. Decline it as the primary hasher on any revenue- or compliance-critical path; the vendor-maintained Hash facade covers that with no new risk.
How can I help you explore Laravel packages today?