Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Complexify Bundle Laravel Package

lightmaker/complexify-bundle

A Symfony bundle that integrates Complexify password-strength checking into forms, providing configurable server-side validation and complexity rules to help enforce stronger passwords consistently across your application.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony 2 bundle, not a Laravel package — so "getting started" means deciding whether you're consuming the bundle or the algorithm. In 2016-era code the bundle is thin: a DI extension registering a complexify service, a Twig/asset hookup for jquery.complexify.js, and (usually) a validator constraint. The valuable part for a Laravel app is the server-side scoring class plus the JS meter.

Step 1 — check whether it can even install.

composer require lightmaker/complexify-bundle --dry-run

The bundle declares symfony/framework-bundle: ~2.x and friends. Modern Laravel ships Symfony 6/7 components (http-foundation, console, process), so the solver will usually refuse. Don't fight it. If it does resolve, you've likely downgraded Symfony internals you actually need — revert.

Step 2 — read the bundle in this order (clone it, or browse GitHub):

  1. DependencyInjection/ — what service IDs and parameters it registers (complexify, complexify.min_complexity, ban-list path).
  2. Resources/config/services.xml — constructor arguments, especially the dictionary/ban-list file.
  3. The Complexify class itself — this is Michael Crumley's PHP port, a single self-contained file with evaluateSecurity($password) returning something like ->valid and ->complexity (0–100).
  4. Resources/views/ and Resources/public/js/ — the Twig template and the jQuery plugin you'll copy into resources/js/vendor/.
  5. Validator/ or Form/ — how the bundle turned the score into a constraint; you'll rewrite this as a Laravel rule.

Step 3 — first use case: registration + password change. Live strength meter on the client, hard floor on the server. That's the whole day-to-day value.

Step 4 — extract, don't depend. Copy the single algorithm class into app/Support/Complexify.php (keep the MIT header) or install the upstream mcrumley/php-complexify package directly. Treat the bundle as a reference implementation, not a dependency: last release 2016, zero dependents, zero stars means nobody is patching it for you.

Implementation Patterns

Pattern A — the extracted service as a container singleton.

// app/Support/Complexify.php  (copied from the bundle, MIT header retained)
namespace App\Support;

class Complexify
{
    /** @var array<string,true> */
    private array $banned = [];

    public function __construct(?string $banListPath = null)
    {
        $path = $banListPath ?? resource_path('data/common-passwords.txt');

        if (is_readable($path)) {
            foreach (file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
                $this->banned[strtolower(trim($line))] = true;
            }
        }
    }

    /** @return object{valid: bool, complexity: float} */
    public function evaluateSecurity(string $password): object
    {
        // bundle's algorithm: length, character classes, repeats, dictionary hits
    }
}
// app/Providers/AppServiceProvider.php
public function register(): void
{
    $this->app->singleton(Complexify::class, fn () => new Complexify(
        banListPath: config('auth.complexify.ban_list'),
    ));
}

Bind it as a singleton so the ban list is parsed once per request/worker, not once per field.

Pattern B — a Laravel validation rule wrapping it.

namespace App\Rules;

use App\Support\Complexify;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class ComplexEnough implements ValidationRule
{
    public function __construct(
        private int $minComplexity = 50,
        private int $minLength = 10,
    ) {}

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (! is_string($value) || $value === '') {
            return; // let 'required' / 'string' report it
        }

        if (mb_strlen($value) < $this->minLength) {
            $fail('The :attribute must be at least '.$this->minLength.' characters.');
            return;
        }

        $result = app(Complexify::class)->evaluateSecurity($value);

        if (! $result->valid || $result->complexity < $this->minComplexity) {
            $fail('The :attribute is too easy to guess. Try a longer passphrase.');
        }
    }
}
$request->validate([
    'password' => ['required', 'string', 'max:255', 'confirmed', new ComplexEnough(minComplexity: 50)],
]);

Pattern C — client meter, wired through Vite/Mix. Copy jquery.complexify.js out of the bundle's Resources/public/ into resources/js/vendor/, then:

{{-- resources/views/auth/partials/password.blade.php --}}
<div>
    <label for="password">Password</label>
    <input id="password" name="password" type="password"
           data-complexify data-min-complexity="50"
           @error('password') aria-invalid="true" aria-describedby="password-error" @enderror>
    <div class="meter" aria-hidden="true"><span id="password-meter"></span></div>
    <p id="password-strength" aria-live="polite"></p>
    @error('password')<p id="password-error">{{ $message }}</p>@enderror
</div>
// resources/js/app.js
import $ from 'jquery';
import './vendor/jquery.complexify';

$(function () {
    const $input = $('[data-complexify]');
    if (!$input.length) return;

    const $meter = $('#password-meter');
    const $label = $('#password-strength');
    const min = Number($input.data('min-complexity') ?? 50);

    $input.complexify({ minimumChars: 10, strengthScaleFactor: 1 }, (valid, complexity) => {
        const pct = Math.round(complexity);
        $meter.css('width', `${pct}%`);
        $label.text(pct >= min ? `Strong (${pct}%)` : `Too weak (${pct}%)`);
    });
});

Pattern D — keep both sides honest. Use the same minimum (50 here), the same minimum length, and ideally the same ban list on both sides. The JS plugin's dictionary and the PHP port's dictionary drift; if you can't pin them, treat the client score as a hint only and let the server be the single source of truth.

Pattern E — test it like any other rule.

public function test_common_passwords_are_rejected(): void
{
    $this->post('/register', [
        'name' => 'Ada',
        'email' => '[email protected]',
        'password' => 'Password1',
        'password_confirmation' => 'Password1',
    ])->assertSessionHasErrors('password');
}

public function test_passphrases_are_accepted(): void
{
    $password = 'correct horse battery staple';

    $this->post('/register', [
        'name' => 'Ada',
        'email' => '[email protected]',
        'password' => $password,
        'password_confirmation' => $password,
    ])->assertRedirect('/dashboard');
}

Pattern F — only then hash. Validate, then Hash::make($password). Never score the hash, and never store the score.

Gotchas and Tips

  • The composer solve is the first bug report. Symfony 2 constraints against Illuminate's Symfony 6/7 dependencies will either fail or silently pin you to ancient components. Check with --dry-run before touching composer.json, and be prepared to abandon the bundle entirely.
  • Abandonware posture. Last release April 2016, no dependents, no stars: assume no security patches. Vendor the file, keep the MIT notice, and add your own tests around it. Don't let it become an untouchable vendor/ dependency.
  • Laravel already ships most of this. Password::min(10)->letters()->numbers()->uncompromised() covers length, classes, and breach checking via HIBP. complexify adds a client-side entropy meter and a small dictionary — that's the marginal value. If you're on Laravel 10/11, consider zxcvbn-php for the server rule and a maintained meter on the client; the upgrade is usually a few hours and buys you a dictionary that isn't from 2014.
  • Complexity ≠ strength. The heuristic (length buckets, repeats, character-class bonuses, dictionary penalty) is capped at 100 and can score a long, high-entropy passphrase poorly if it's all lowercase. Don't set minComplexity so high that correct horse battery staple fails — you'll train users to pick P@ssw0rd1! instead.
  • Client score is bypassable and sometimes divergent. Anyone can POST past the meter, and the PHP port's dictionary may differ from the JS one. Enforce server-side, document that the meter is advisory, and don't mirror a hard client-side block that could lock a legitimate user out.
  • Watch the ban-list load. Naive implementations file() the dictionary on every evaluateSecurity() call. Parse once in the constructor and register the class as a singleton; in Octane this matters more, not less.
  • Length limits interact with hashing. max:255 for the input is fine, but bcrypt truncates at 72 bytes. Validate max length explicitly so a 200-char passphrase doesn't get silently truncated into something weaker. Argon2id avoids the issue.
  • Configuration quirks are Symfony-shaped. The bundle's config.yml keys (complexify.min_complexity, dictionary path) don't map to Laravel config; recreate them under config/auth.php or a dedicated config/complexify.php and reference them from the rule constructor so thresholds are tunable without code edits.
  • Extension points worth using: the ban list/dictionary file (replace with a larger, current common-password list) and the minimum-complexity threshold per form (stricter on registration and password change, looser on invite-acceptance). Everything else is better rewritten as a rule than adapted from Symfony's Constraint.
  • Accessibility of the meter. aria-live="polite" on the strength label and aria-describedby from the input to the error are the two things people forget; the meter <div> should be aria-hidden.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager